├── helpers ├── email │ ├── index.js │ ├── templates.js │ └── sendEmail.js ├── google │ ├── maps.js │ └── getTzAndLoc.js ├── mixpanel.js ├── privacy │ └── showLocationPolicy.js ├── analytics │ ├── types.js │ └── index.js ├── rest │ ├── index.js │ └── twivatar.js ├── auth │ ├── session.js │ ├── anonymous.js │ └── jwt.js ├── gravatar.js ├── errors.js └── github.js ├── resolvers ├── index.js ├── unpinUser.js ├── removeManualPlace.js ├── unfollow.js ├── removeManualPerson.js ├── followUser.js ├── manualPlace.js ├── manualPerson.js ├── pinUser.js ├── unpinManualPlace.js ├── unpinManualPerson.js ├── updateTimezone.js ├── deleteAccount.js ├── updateLocationAndTimezone.js ├── shared │ └── countPinned.js ├── countryFlagEmoji.js ├── updateLocationAndTimezoneForUser.js ├── pinManualPerson.js ├── pinManualPlace.js ├── updateUser.js ├── placesAutoComplete.js ├── addManualPlace.js ├── countryFlagIcon.js ├── updateManualPerson.js ├── updateManualPlace.js ├── addManualPerson.js ├── allUsersByName.js └── sortFollowings.js ├── static └── assets │ ├── favicon.png │ └── country-flags │ ├── JP.svg │ ├── BD.svg │ ├── PW.svg │ ├── ID.svg │ ├── MC.svg │ ├── UA.svg │ ├── PL.svg │ ├── SO.svg │ ├── VN.svg │ ├── YE.svg │ ├── CH.svg │ ├── FR.svg │ ├── IE.svg │ ├── IT.svg │ ├── LU.svg │ ├── NL.svg │ ├── EE.svg │ ├── LV.svg │ ├── NG.svg │ ├── PE.svg │ ├── MG.svg │ ├── BH.svg │ ├── CO.svg │ ├── CZ.svg │ ├── MT.svg │ ├── RU.svg │ ├── BJ.svg │ ├── FI.svg │ ├── GL.svg │ ├── BF.svg │ ├── BE.svg │ ├── QA.svg │ ├── AE.svg │ ├── DE.svg │ ├── HU.svg │ ├── ML.svg │ ├── SL.svg │ ├── AM.svg │ ├── BG.svg │ ├── BO.svg │ ├── CI.svg │ ├── GA.svg │ ├── IM.svg │ ├── LT.svg │ ├── TD.svg │ ├── TO.svg │ ├── LB.svg │ ├── PS.svg │ ├── ES.svg │ ├── GN.svg │ ├── AT.svg │ ├── CL.svg │ ├── GM.svg │ ├── MV.svg │ ├── BS.svg │ ├── CR.svg │ ├── TH.svg │ ├── RO.svg │ ├── CG.svg │ ├── NE.svg │ ├── MR.svg │ ├── LA.svg │ ├── TL.svg │ ├── JM.svg │ ├── MU.svg │ ├── KW.svg │ ├── SD.svg │ ├── LI.svg │ ├── TZ.svg │ ├── LS.svg │ ├── SI.svg │ ├── TR.svg │ ├── CN.svg │ ├── DK.svg │ ├── GH.svg │ ├── IN.svg │ ├── MW.svg │ ├── SE.svg │ ├── MA.svg │ ├── NF.svg │ ├── MM.svg │ ├── AR.svg │ ├── CM.svg │ ├── DJ.svg │ ├── EG.svg │ ├── CA.svg │ ├── PA.svg │ ├── SN.svg │ ├── TN.svg │ ├── SC.svg │ ├── BR.svg │ ├── PT.svg │ ├── SY.svg │ ├── CX.svg │ ├── HT.svg │ ├── NR.svg │ ├── JO.svg │ ├── ZM.svg │ ├── BW.svg │ ├── GW.svg │ ├── SR.svg │ ├── DZ.svg │ ├── AL.svg │ ├── GG.svg │ ├── KP.svg │ ├── VC.svg │ ├── TG.svg │ ├── GR.svg │ ├── KH.svg │ ├── RW.svg │ ├── MX.svg │ ├── PR.svg │ ├── TT.svg │ ├── KZ.svg │ ├── AW.svg │ ├── OM.svg │ ├── PY.svg │ ├── PF.svg │ ├── ST.svg │ ├── AG.svg │ ├── FM.svg │ ├── JE.svg │ ├── TW.svg │ ├── BB.svg │ ├── SV.svg │ ├── MH.svg │ ├── AF.svg │ ├── FO.svg │ ├── IS.svg │ ├── NI.svg │ ├── NO.svg │ └── BZ.svg ├── .gitignore ├── .npmignore ├── index.js ├── utils ├── config.js ├── normalizeById.js └── randomPhrase.js ├── schema └── index.js └── now.json /helpers/email/index.js: -------------------------------------------------------------------------------- 1 | export * from './templates' 2 | -------------------------------------------------------------------------------- /resolvers/index.js: -------------------------------------------------------------------------------- 1 | import resolvers from './resolvers' 2 | export default resolvers 3 | -------------------------------------------------------------------------------- /static/assets/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/therehq/there-server/HEAD/static/assets/favicon.png -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | *.log 4 | 5 | # Secrets 6 | .env 7 | now-secrets.json 8 | secrets/ 9 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | package-lock.json 3 | *.log 4 | 5 | # Secrets 6 | .env 7 | now-secrets.json 8 | # secrets/ 9 | -------------------------------------------------------------------------------- /helpers/google/maps.js: -------------------------------------------------------------------------------- 1 | export const client = require('@google/maps').createClient({ 2 | key: process.env.GOOGLE_API_KEY, 3 | Promise: global.Promise, 4 | }) 5 | -------------------------------------------------------------------------------- /resolvers/unpinUser.js: -------------------------------------------------------------------------------- 1 | export default async (obj, args, ctx, info) => { 2 | // Unpin 3 | await ctx.user.removePinnedUsers(args.userId) 4 | return { userId: args.userId } 5 | } 6 | -------------------------------------------------------------------------------- /helpers/mixpanel.js: -------------------------------------------------------------------------------- 1 | import Mixpanel from 'mixpanel' 2 | 3 | // Utilities 4 | import config from '../utils/config' 5 | 6 | export const mixpanel = Mixpanel.init(config.mixpanelProjectToken) 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | // Set options as a parameter, environment variable, or rc file. 3 | require = require('esm')(module /*, options*/) 4 | module.exports = require('./main.js') 5 | -------------------------------------------------------------------------------- /helpers/privacy/showLocationPolicy.js: -------------------------------------------------------------------------------- 1 | const ALWAYS = 'always' 2 | const NEVER = 'never' 3 | 4 | export const types = { 5 | ALWAYS, 6 | NEVER, 7 | } 8 | 9 | export const allTypes = [ALWAYS, NEVER] 10 | -------------------------------------------------------------------------------- /helpers/analytics/types.js: -------------------------------------------------------------------------------- 1 | export const eventTypes = { 2 | OPEN: 'open', 3 | PING: 'ping', 4 | TRAY_CLICK: 'tray-click', 5 | QUIT: 'quit', 6 | } 7 | 8 | export const allEventTypes = Object.values(eventTypes) 9 | -------------------------------------------------------------------------------- /resolvers/removeManualPlace.js: -------------------------------------------------------------------------------- 1 | import { User } from '../models' 2 | import followingList from './followingList' 3 | 4 | export default async (obj, { id }, ctx, info) => { 5 | // Remove place 6 | await ctx.user.removeManualPlace(id) 7 | 8 | return { id } 9 | } 10 | -------------------------------------------------------------------------------- /resolvers/unfollow.js: -------------------------------------------------------------------------------- 1 | import { User } from '../models' 2 | import followingList from './followingList' 3 | 4 | export default async (obj, { userId }, ctx, info) => { 5 | // Unfollow 6 | await ctx.user.removeFollowing(userId) 7 | 8 | return { id: userId } 9 | } 10 | -------------------------------------------------------------------------------- /resolvers/removeManualPerson.js: -------------------------------------------------------------------------------- 1 | import { User } from '../models' 2 | import followingList from './followingList' 3 | 4 | export default async (obj, { id }, ctx, info) => { 5 | // Remove person 6 | await ctx.user.removeManualPerson(id) 7 | 8 | return { id } 9 | } 10 | -------------------------------------------------------------------------------- /resolvers/followUser.js: -------------------------------------------------------------------------------- 1 | import followingList from './followingList' 2 | 3 | export default async (obj, args, ctx, info) => { 4 | // Follow 5 | await ctx.user.addFollowing(args.userId) 6 | const followedUser = await ctx.models.User.findById(args.userId) 7 | 8 | return followedUser 9 | } 10 | -------------------------------------------------------------------------------- /resolvers/manualPlace.js: -------------------------------------------------------------------------------- 1 | import { ManualPlace } from '../models' 2 | 3 | export default async (obj, args, ctx) => { 4 | if (!args.id) { 5 | throw new Error('No place ID was sent.') 6 | } 7 | 8 | const place = await ManualPlace.findById(args.id) 9 | 10 | if (!place) { 11 | throw new Error('No place matched the ID.') 12 | } 13 | 14 | return place.get({ plain: true }) 15 | } 16 | -------------------------------------------------------------------------------- /resolvers/manualPerson.js: -------------------------------------------------------------------------------- 1 | import { ManualPerson } from '../models' 2 | 3 | export default async (obj, args, ctx) => { 4 | if (!args.id) { 5 | throw new Error('No Person ID was sent.') 6 | } 7 | 8 | const person = await ManualPerson.findById(args.id) 9 | 10 | if (!person) { 11 | throw new Error('No person matched the ID.') 12 | } 13 | 14 | return person.get({ plain: true }) 15 | } 16 | -------------------------------------------------------------------------------- /helpers/email/templates.js: -------------------------------------------------------------------------------- 1 | import sendEmail from './sendEmail' 2 | 3 | export const EMAIL_VERIFICATION = '6591781' 4 | export const sendEmailVerification = ( 5 | To, 6 | { securityCode, callbackUrl, firstName }, 7 | ) => 8 | sendEmail({ 9 | To, 10 | TemplateId: EMAIL_VERIFICATION, 11 | TemplateModel: { 12 | securityCode, 13 | callbackUrl, 14 | firstName: firstName || '', 15 | }, 16 | }) 17 | -------------------------------------------------------------------------------- /utils/config.js: -------------------------------------------------------------------------------- 1 | const { NOW } = process.env 2 | const host = NOW ? 'https://apiv1.there.team' : 'http://localhost:9900' 3 | const mixpanelProjectToken = NOW 4 | ? 'e7859c5640d175b8f34d425735fba85e' // PROD 5 | : '31a53a5d9fb1a091846b5abffac684e7' // DEV 6 | 7 | export default { 8 | apiUrl: host, 9 | mixpanelProjectToken, 10 | 11 | // users 12 | maxPinned: 4, 13 | 14 | // flags 15 | countryFlagsHash: 'EkD3d', 16 | } 17 | -------------------------------------------------------------------------------- /resolvers/pinUser.js: -------------------------------------------------------------------------------- 1 | import config from '../utils/config' 2 | import { countPinned } from './shared/countPinned' 3 | 4 | export default async (obj, args, ctx, info) => { 5 | const pinnedCount = await countPinned(ctx.user) 6 | 7 | if (pinnedCount >= config.maxPinned) { 8 | return new Error('Max pinned limit reached') 9 | } 10 | 11 | // Pin 12 | await ctx.user.addPinnedUsers(args.userId) 13 | return { userId: args.userId } 14 | } 15 | -------------------------------------------------------------------------------- /utils/normalizeById.js: -------------------------------------------------------------------------------- 1 | export default arrayOfObjectsWithId => { 2 | if ( 3 | typeof arrayOfObjectsWithId !== 'object' || 4 | !arrayOfObjectsWithId.length 5 | ) { 6 | throw new Error(`It can only normalize arrays.`) 7 | } 8 | 9 | let byId = {} 10 | let ids = [] 11 | 12 | for (let { id, ...otherProps } of arrayOfObjectsWithId) { 13 | byId[id] = otherProps 14 | ids.push(id) 15 | } 16 | 17 | return { byId, ids } 18 | } 19 | -------------------------------------------------------------------------------- /helpers/rest/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | 3 | // Controllers 4 | import uploadManualPhoto from './uploadManualPhoto' 5 | import twivatar from './twivatar' 6 | 7 | const router = Router() 8 | export default router 9 | 10 | // API Intro! 11 | router.get('/', (req, res) => { 12 | res.send('REST API for There! 😴 REST?') 13 | }) 14 | 15 | router.post('/upload-manual-photo', ...uploadManualPhoto()) 16 | router.get('/twivatar/:user', twivatar) 17 | -------------------------------------------------------------------------------- /resolvers/unpinManualPlace.js: -------------------------------------------------------------------------------- 1 | import { ManualPlace } from '../models' 2 | 3 | export default async (obj, { id }, ctx) => { 4 | const [affected] = await ManualPlace.update( 5 | { pinned: false, pinnedAt: null }, 6 | { where: { id } }, 7 | ) 8 | 9 | if (affected === 1) { 10 | // Later if we had a better caching in the app, 11 | // we should return the updated row 12 | return { pinned: false } 13 | } 14 | 15 | // Nothing affected 16 | return null 17 | } 18 | -------------------------------------------------------------------------------- /resolvers/unpinManualPerson.js: -------------------------------------------------------------------------------- 1 | import { ManualPerson } from '../models' 2 | 3 | export default async (obj, { id }, ctx) => { 4 | const [affected] = await ManualPerson.update( 5 | { pinned: false, pinnedAt: null }, 6 | { where: { id } }, 7 | ) 8 | 9 | if (affected === 1) { 10 | // Later if we had a better caching in the app, 11 | // we should return the updated row 12 | return { pinned: false } 13 | } 14 | 15 | // Nothing affected 16 | return null 17 | } 18 | -------------------------------------------------------------------------------- /helpers/analytics/index.js: -------------------------------------------------------------------------------- 1 | import { Router } from 'express' 2 | 3 | const router = Router() 4 | 5 | // API Intro! 6 | router.get('/', (req, res) => { 7 | res.send('Analytics API for There! 🔄 / ⚠️ Deprecated') 8 | }) 9 | 10 | // ⚠️ MAINTAIN Mode - using mixpanel instead 11 | // Submit events from the app 12 | router.post('/event', async (req, res) => { 13 | // Send the response 14 | res.json({ ok: true, analyticsEvent: {}, message: '⚠️ Deprecated' }) 15 | }) 16 | 17 | export default router 18 | -------------------------------------------------------------------------------- /helpers/auth/session.js: -------------------------------------------------------------------------------- 1 | import { User } from '../../models' 2 | 3 | export const serializeUser = (user, done) => { 4 | done(null, user.id) 5 | } 6 | 7 | export const deserializeUser = (id, done) => { 8 | if (!id) { 9 | done(null, {}) 10 | return 11 | } 12 | 13 | User.findById(id) 14 | .then(user => { 15 | if (user) { 16 | done(null, user.get()) 17 | } else { 18 | done(null, {}) 19 | } 20 | }) 21 | .catch(error => done(error, {})) 22 | } 23 | -------------------------------------------------------------------------------- /resolvers/updateTimezone.js: -------------------------------------------------------------------------------- 1 | export default async (obj, args, ctx) => { 2 | // Check if email is already registered 3 | const [affected] = await ctx.models.User.update( 4 | { timezone: args.timezone, city: '', fullLocation: '' }, 5 | { 6 | where: { id: ctx.userId }, 7 | }, 8 | ) 9 | 10 | // Return user 11 | if (affected === 1) { 12 | const user = await ctx.models.User.findById(ctx.userId) 13 | return user.dataValues 14 | } 15 | 16 | // Nothing affected 17 | return null 18 | } 19 | -------------------------------------------------------------------------------- /resolvers/deleteAccount.js: -------------------------------------------------------------------------------- 1 | import { mixpanel } from '../helpers/mixpanel' 2 | 3 | export default async (obj, args, ctx, info) => { 4 | const userId = ctx.userId 5 | 6 | if (!ctx.user) { 7 | return new Error('No user') 8 | } 9 | 10 | try { 11 | mixpanel.track('Delete Account', { 12 | distinct_id: userId, 13 | userId, 14 | }) 15 | } catch (err) { 16 | console.log(err) 17 | } 18 | 19 | // Delete user and its data 20 | await ctx.user.destroy({ force: true }) 21 | return true 22 | } 23 | -------------------------------------------------------------------------------- /resolvers/updateLocationAndTimezone.js: -------------------------------------------------------------------------------- 1 | import getTimezoneAndLocation from '../helpers/google/getTzAndLoc' 2 | 3 | export default async (obj, { placeId }, ctx) => { 4 | const { city, fullLocation, timezone } = await getTimezoneAndLocation(placeId) 5 | 6 | // Check for signed in user 7 | if (!ctx.user) { 8 | throw new Error('Unauthorized') 9 | } 10 | 11 | // Update user with data! 12 | const res = await ctx.user.update({ 13 | city, 14 | fullLocation, 15 | timezone, 16 | }) 17 | 18 | return res.get({ plain: true }) 19 | } 20 | -------------------------------------------------------------------------------- /helpers/gravatar.js: -------------------------------------------------------------------------------- 1 | import qs from 'qs' 2 | import crypto from 'crypto' 3 | 4 | const BASE_URL = 'https://gravatar.com/avatar/' 5 | 6 | export const getGravatarUrl = (email, opts) => { 7 | if (email.indexOf('@') === -1) { 8 | throw new Error('Please specify an email') 9 | } 10 | 11 | const query = qs.stringify(opts) 12 | const neatEmail = email.toLowerCase().trim() 13 | const hash = crypto 14 | .createHash('md5') 15 | .update(neatEmail) 16 | .digest('hex') 17 | 18 | return BASE_URL + `${hash}.jpg` + (query ? `?${query}` : '') 19 | } 20 | -------------------------------------------------------------------------------- /resolvers/shared/countPinned.js: -------------------------------------------------------------------------------- 1 | import { ManualPerson, ManualPlace } from '../../models' 2 | 3 | export const countPinned = async user => { 4 | const pinnedManualPeople = () => 5 | user.countManualPeople({ where: { pinned: true } }) 6 | const pinnedManualPlaces = () => 7 | user.countManualPlaces({ where: { pinned: true } }) 8 | const pinnedUsers = () => user.countPinnedUsers() 9 | 10 | const counts = await Promise.all([ 11 | pinnedManualPeople(), 12 | pinnedManualPlaces(), 13 | pinnedUsers(), 14 | ]) 15 | 16 | return counts.reduce((sum, c) => sum + c, 0) 17 | } 18 | -------------------------------------------------------------------------------- /schema/index.js: -------------------------------------------------------------------------------- 1 | import { makeExecutableSchema } from 'graphql-tools' 2 | 3 | import resolvers from '../resolvers' 4 | import typeDefs from './typeDefs' 5 | import * as OrmModels from '../models' 6 | import { asyncErrorHandler } from '../helpers/errors' 7 | 8 | export const schema = makeExecutableSchema({ 9 | typeDefs, 10 | resolvers, 11 | }) 12 | 13 | export const models = OrmModels 14 | 15 | // Fetch current logged in user a single time, 16 | // so we don't refetch it in every resolver 17 | export const getUser = asyncErrorHandler( 18 | async userId => await OrmModels.User.findById(userId), 19 | ) 20 | -------------------------------------------------------------------------------- /resolvers/countryFlagEmoji.js: -------------------------------------------------------------------------------- 1 | import { flag as getFlagEmoji } from 'country-emoji' 2 | import config from '../utils/config' 3 | 4 | export default (obj, args, ctx) => { 5 | const { fullLocation } = obj 6 | 7 | if (!fullLocation) { 8 | return null 9 | } 10 | 11 | let locationParts 12 | if (fullLocation.includes(',')) { 13 | locationParts = fullLocation.split(',') 14 | } else if (fullLocation.includes('-')) { 15 | locationParts = fullLocation.split('-') 16 | } else { 17 | locationParts = fullLocation 18 | } 19 | 20 | const countryName = locationParts[locationParts.length - 1] 21 | 22 | return getFlagEmoji(countryName) 23 | } 24 | -------------------------------------------------------------------------------- /resolvers/updateLocationAndTimezoneForUser.js: -------------------------------------------------------------------------------- 1 | import { User } from '../models' 2 | import getTimezoneAndLocation from '../helpers/google/getTzAndLoc' 3 | 4 | export default async (obj, { placeId, userId }, ctx) => { 5 | const { city, fullLocation, timezone } = await getTimezoneAndLocation(placeId) 6 | 7 | // Check for signed in user 8 | if (!userId || !placeId) { 9 | throw new Error('Data missing') 10 | } 11 | 12 | // Update user with data! 13 | await User.update( 14 | { 15 | city, 16 | fullLocation, 17 | timezone, 18 | }, 19 | { where: { id: userId } }, 20 | ) 21 | 22 | const res = await User.findById(userId) 23 | 24 | return res.get({ plain: true }) 25 | } 26 | -------------------------------------------------------------------------------- /resolvers/pinManualPerson.js: -------------------------------------------------------------------------------- 1 | import { ManualPerson } from '../models' 2 | import config from '../utils/config' 3 | import { countPinned } from './shared/countPinned' 4 | 5 | export default async (obj, { id }, ctx) => { 6 | const pinnedCount = await countPinned(ctx.user) 7 | 8 | if (pinnedCount >= config.maxPinned) { 9 | return new Error('Max pinned limit reached') 10 | } 11 | 12 | const [affected] = await ManualPerson.update( 13 | { pinned: true, pinnedAt: Date.now() }, 14 | { where: { id } }, 15 | ) 16 | 17 | if (affected === 1) { 18 | // Later if we had a better caching in the app, 19 | // we should return the updated row 20 | return { pinned: true } 21 | } 22 | 23 | // Nothing affected 24 | return null 25 | } 26 | -------------------------------------------------------------------------------- /resolvers/pinManualPlace.js: -------------------------------------------------------------------------------- 1 | import { ManualPlace } from '../models' 2 | import { countPinned } from './shared/countPinned' 3 | import config from '../utils/config' 4 | 5 | export default async (obj, { id }, ctx) => { 6 | const pinnedCount = await countPinned(ctx.user) 7 | 8 | if (pinnedCount >= config.maxPinned) { 9 | return new Error('Max pinned limit reached') 10 | } 11 | 12 | const [affected] = await ManualPlace.update( 13 | { pinned: true, pinnedAt: Date.now() }, 14 | { where: { id } }, 15 | ) 16 | 17 | if (affected === 1) { 18 | // Later if we had a better caching in the app, 19 | // we should return the updated row 20 | return { pinned: true } 21 | } 22 | 23 | // Nothing affected 24 | return null 25 | } 26 | -------------------------------------------------------------------------------- /resolvers/updateUser.js: -------------------------------------------------------------------------------- 1 | export default async (obj, args, ctx) => { 2 | // Check if email is already registered 3 | if (args.email) { 4 | const userWithEmail = await ctx.models.User.findOne({ 5 | where: { email: args.email }, 6 | }) 7 | if (userWithEmail) { 8 | const registeredEmail = args.email 9 | delete args.email 10 | return new Error(`${registeredEmail} is registered. Sign in by email!`) 11 | } 12 | } 13 | 14 | const [affected] = await ctx.models.User.update(args, { 15 | where: { id: ctx.userId }, 16 | }) 17 | // Return user 18 | if (affected === 1) { 19 | const user = await ctx.models.User.findById(ctx.userId) 20 | return user.dataValues 21 | } 22 | // Nothing affected 23 | return {} 24 | } 25 | -------------------------------------------------------------------------------- /static/assets/country-flags/JP.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /static/assets/country-flags/BD.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /static/assets/country-flags/PW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /resolvers/placesAutoComplete.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven' 2 | 3 | export default async (obj, args, ctx) => { 4 | try { 5 | const googleMaps = require('@google/maps').createClient({ 6 | key: process.env.GOOGLE_API_KEY, 7 | Promise: global.Promise, 8 | }) 9 | 10 | const { json: { predictions } } = await googleMaps 11 | .placesAutoComplete({ 12 | input: args.query, 13 | language: 'en', 14 | types: '(cities)', 15 | }) 16 | .asPromise() 17 | 18 | if (typeof predictions !== 'undefined' && predictions.length > 0) { 19 | return predictions.map(({ description, place_id }) => { 20 | return { description, placeId: place_id } 21 | }) 22 | } else { 23 | return [] 24 | } 25 | } catch (e) { 26 | Raven.captureException(e) 27 | return [] 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /static/assets/country-flags/ID.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /static/assets/country-flags/MC.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /static/assets/country-flags/UA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /static/assets/country-flags/PL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /helpers/google/getTzAndLoc.js: -------------------------------------------------------------------------------- 1 | import { client as mapsClient } from './maps' 2 | 3 | export default async placeId => { 4 | if (!placeId) { 5 | return {} 6 | } 7 | 8 | const { 9 | json: { result }, 10 | } = await mapsClient 11 | .place({ 12 | placeid: placeId, 13 | language: 'en', 14 | }) 15 | .asPromise() 16 | 17 | // Extract and rename information for saving in database 18 | const { 19 | name: city, 20 | formatted_address: fullLocation, 21 | geometry: { 22 | location: { lat, lng }, 23 | }, 24 | } = result 25 | 26 | // Fetch timezone string based on geometry (Lat/Lng) 27 | const { 28 | json: { timeZoneId: timezone }, 29 | } = await mapsClient 30 | .timezone({ 31 | location: [lat, lng], 32 | timestamp: Date.now() / 1000, 33 | }) 34 | .asPromise() 35 | 36 | // Return data in correct format 37 | return { 38 | city, 39 | fullLocation, 40 | timezone, 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /static/assets/country-flags/SO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /static/assets/country-flags/VN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /helpers/auth/anonymous.js: -------------------------------------------------------------------------------- 1 | import v4 from 'uuid/v4' 2 | 3 | // Utilities 4 | import { mixpanel } from '../mixpanel' 5 | import { encodeJwt } from './jwt' 6 | 7 | // Models 8 | import { User } from '../../models' 9 | 10 | export const loginAnonymously = async (req, res, next) => { 11 | try { 12 | // Gen a new userId 13 | const userId = v4() 14 | 15 | const token = encodeJwt(userId) 16 | 17 | // Create user 18 | const user = await User.create({ 19 | id: userId, 20 | isAnonymous: true, 21 | }) 22 | 23 | // Track sign up 24 | try { 25 | mixpanel.track('Sign Up', { 26 | distinct_id: userId, 27 | method: 'anonymous', 28 | }) 29 | } catch (err) { 30 | console.log(err) 31 | } 32 | 33 | // Pass user data 34 | req.user = user.get({ plain: true }) 35 | 36 | res.json({ token, user }) 37 | // next() 38 | } catch (err) { 39 | console.error(err) 40 | return next(err) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /static/assets/country-flags/YE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /static/assets/country-flags/CH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /helpers/errors.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven' 2 | 3 | /** 4 | * 5 | * @param {Function} children 6 | */ 7 | export const asyncErrorHandler = children => (...args) => { 8 | try { 9 | return children(...args) 10 | } catch (e) { 11 | Raven.captureException(e) 12 | console.error(e) 13 | return 14 | } 15 | } 16 | 17 | export const capture = Raven.captureException 18 | 19 | export const expressJsonErrorHandler = (err, req, res, next) => { 20 | if (res.headersSent) { 21 | return next(err) 22 | } 23 | 24 | if (!err) { 25 | return next() 26 | } 27 | 28 | const id = Raven.captureException(err) 29 | 30 | res 31 | .status(500) 32 | .send( 33 | `Oops, something went wrong! Our engineers have been alerted and will fix this asap. (Error Id : ${id})
Chat support is online: https://go.crisp.chat/chat/embed/?website_id=bb14ccd2-0869-40e7-b0f1-b520e93db7e1`, 34 | ) 35 | } 36 | -------------------------------------------------------------------------------- /static/assets/country-flags/FR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /static/assets/country-flags/IE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /static/assets/country-flags/IT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /static/assets/country-flags/LU.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /static/assets/country-flags/NL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /now.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": 1, 3 | "name": "there-server", 4 | "alias": "apiv1.there.team", 5 | "env": { 6 | "GOOGLE_API_KEY": "@there-google-api-key", 7 | "FIREBASE_ADMIN_SDK": "@there-firebase-admin-sdk", 8 | "SENTRY_DSN": "@there-api-sentry-dsn", 9 | "DB_HOST": "@there-main-db-host", 10 | "DB_NAME": "@there-main-db-name", 11 | "DB_USERNAME": "@there-main-db-username", 12 | "DB_PASSWORD": "@there-main-db-password", 13 | "TWITTER_CONSUMER_KEY": "@there-twitter-consumer-key", 14 | "TWITTER_CONSUMER_SECRET": "@there-twitter-consumer-secret", 15 | "SLACK_CLIENT_ID": "@there-slack-client-id", 16 | "SLACK_CLIENT_SECRET": "@there-slack-client-secret", 17 | "SESSION_SECRET": "@there-session-secret", 18 | "JWT_SECRET": "@there-jwt-secret", 19 | "GH_TOKEN_RELEASES": "@there-github-token-releases", 20 | "MAILJET_KEY_PUBLIC": "@there-mailjet-key-public", 21 | "MAILJET_KEY_PRIVATE": "@there-mailjet-key-private", 22 | "POSTMARK_SERVER_KEY": "@there-postmark-server-key" 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /static/assets/country-flags/EE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/LV.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/NG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/PE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/MG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /helpers/email/sendEmail.js: -------------------------------------------------------------------------------- 1 | import postmark from 'postmark' 2 | import fs from 'mz/fs' 3 | import path from 'path' 4 | const debug = require('debug')('send-email') 5 | 6 | const client = new postmark.Client(process.env.POSTMARK_SERVER_KEY) 7 | 8 | const sendEmail = async ({ To, TemplateId, TemplateModel }) => { 9 | return new Promise((res, rej) => { 10 | client.sendEmailWithTemplate( 11 | { 12 | From: 'support@there.team', 13 | To, 14 | TemplateId, 15 | TemplateModel, 16 | }, 17 | async err => { 18 | if (err) { 19 | // 406 means the user became inactive, either by having an email 20 | // hard bounce or they marked as spam 21 | if (err.code === 406) { 22 | debug(`Email user is deactive: ${to}`) 23 | } 24 | 25 | console.error('Error sending email:') 26 | console.error(err) 27 | return rej(err) 28 | } 29 | res() 30 | debug(`email to ${To} sent successfully`) 31 | }, 32 | ) 33 | }) 34 | } 35 | 36 | export default sendEmail 37 | -------------------------------------------------------------------------------- /static/assets/country-flags/BH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/CO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/CZ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/MT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/RU.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/BJ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /helpers/rest/twivatar.js: -------------------------------------------------------------------------------- 1 | import cheerio from 'cheerio' 2 | import Debug from 'debug' 3 | import bent from 'bent' 4 | const getString = bent('string') 5 | 6 | const debug = Debug('twivatar') 7 | const oneMonthInSec = 2628000 8 | 9 | const get = username => { 10 | const url = 'https://mobile.twitter.com/' + username 11 | return new Promise((resolve, reject) => { 12 | getString(url) 13 | .then(body => { 14 | const $ = cheerio.load(body) 15 | 16 | resolve( 17 | ($('.avatar img').attr('src') || '').replace('_normal', '_80x80'), 18 | ) 19 | }) 20 | .catch(reject) 21 | }) 22 | } 23 | 24 | export default async (req, res, next) => { 25 | let imageUrl 26 | try { 27 | imageUrl = await get(req.params.user) 28 | } catch (error) { 29 | console.warn(error) 30 | } 31 | 32 | debug(`Fetching Twitter avatar for ${req.params.user}...`) 33 | 34 | if (!imageUrl) { 35 | return res.status(404).send('') 36 | } 37 | 38 | res.setHeader('Cache-Control', `public, max-age=${oneMonthInSec}`) 39 | 40 | const stream = await bent()(imageUrl) 41 | stream.pipe(res) 42 | } 43 | -------------------------------------------------------------------------------- /resolvers/addManualPlace.js: -------------------------------------------------------------------------------- 1 | import { ManualPlace } from '../models' 2 | import getTzAndLoc from '../helpers/google/getTzAndLoc' 3 | import followingList from './followingList' 4 | 5 | export default async (obj, args, ctx, info) => { 6 | const { 7 | name, 8 | timezone: inputTimezone, 9 | placeId, 10 | photoUrl, 11 | photoCloudObject, 12 | } = args 13 | 14 | // Find timezone and exact location based on placeId 15 | let { city, fullLocation, timezone } = await getTzAndLoc(placeId) 16 | 17 | // Used for UTC 18 | if (!placeId && !city && inputTimezone) { 19 | city = null 20 | fullLocation = null 21 | timezone = inputTimezone 22 | } 23 | 24 | // Create and save place 25 | const savedPlace = await ManualPlace.create({ 26 | name, 27 | photoUrl, 28 | photoCloudObject, 29 | 30 | city, 31 | fullLocation, 32 | timezone, 33 | }) 34 | 35 | try { 36 | await ctx.user.addManualPlace(savedPlace) 37 | } catch (err) { 38 | Raven.captureException(err) 39 | console.log(err) 40 | return err 41 | } 42 | 43 | return savedPlace.get({ plain: true }) 44 | } 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/FI.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/GL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/BF.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /resolvers/countryFlagIcon.js: -------------------------------------------------------------------------------- 1 | import { code as getCode } from 'country-emoji' 2 | import config from '../utils/config' 3 | 4 | export default (obj, args, ctx) => { 5 | const { fullLocation } = obj 6 | 7 | if (!fullLocation) { 8 | return null 9 | } 10 | 11 | let locationParts 12 | if (fullLocation.includes(',')) { 13 | locationParts = fullLocation.split(',') 14 | } else if (fullLocation.includes('-')) { 15 | locationParts = fullLocation.split('-') 16 | } else { 17 | locationParts = fullLocation 18 | } 19 | 20 | // Check the last part for country name 21 | let countryName = locationParts[locationParts.length - 1] 22 | 23 | let code = getCode(countryName) 24 | 25 | if (!code) { 26 | if (locationParts.length >= 3) { 27 | // Check the second part for country name 28 | countryName = locationParts[locationParts.length - 2] 29 | code = getCode(countryName) 30 | 31 | if (!code) { 32 | return null 33 | } 34 | } else { 35 | return null 36 | } 37 | } 38 | 39 | return `${config.apiUrl}/static/assets/country-flags-${ 40 | config.countryFlagsHash 41 | }/${code}.svg` 42 | } 43 | -------------------------------------------------------------------------------- /resolvers/updateManualPerson.js: -------------------------------------------------------------------------------- 1 | import { ManualPerson } from '../models' 2 | 3 | // Utilities 4 | import getTzAndLoc from '../helpers/google/getTzAndLoc' 5 | 6 | export default async (obj, args, ctx) => { 7 | if (!args.id) { 8 | throw new Error('No ID was specified.') 9 | } 10 | 11 | const { id, placeId, timezone: inputTimezone, ...newValues } = args 12 | 13 | if (Boolean(placeId)) { 14 | // Only update location if it has been changed 15 | const { city, fullLocation, timezone } = await getTzAndLoc(placeId) 16 | // Set location/timezone values to be updated 17 | newValues.city = city 18 | newValues.timezone = timezone 19 | newValues.fullLocation = fullLocation 20 | } 21 | 22 | // UTC 23 | if (Boolean(inputTimezone)) { 24 | newValues.timezone = inputTimezone 25 | } 26 | 27 | const [affected] = await ManualPerson.update(newValues, { 28 | where: { id }, 29 | }) 30 | 31 | if (affected === 1) { 32 | // We don't fetch from DB here, later if we had a 33 | // better caching in the app, we should return the 34 | // new row 35 | return newValues 36 | } 37 | 38 | // Nothing affected 39 | return null 40 | } 41 | -------------------------------------------------------------------------------- /resolvers/updateManualPlace.js: -------------------------------------------------------------------------------- 1 | import { ManualPlace } from '../models' 2 | 3 | // Utilities 4 | import getTzAndLoc from '../helpers/google/getTzAndLoc' 5 | 6 | export default async (obj, args, ctx) => { 7 | if (!args.id) { 8 | throw new Error('No ID was specified.') 9 | } 10 | 11 | const { id, placeId, timezone: inputTimezone, ...newValues } = args 12 | 13 | if (Boolean(placeId)) { 14 | // Only update location if it has been changed 15 | const { city, fullLocation, timezone } = await getTzAndLoc(placeId) 16 | // Set location/timezone values to be updated 17 | newValues.city = city 18 | newValues.timezone = timezone 19 | newValues.fullLocation = fullLocation 20 | } 21 | 22 | // UTC 23 | if (Boolean(inputTimezone)) { 24 | newValues.timezone = inputTimezone 25 | } 26 | 27 | const [affected] = await ManualPlace.update(newValues, { 28 | where: { id }, 29 | }) 30 | 31 | if (affected === 1) { 32 | // We don't fetch from DB here, later if we had a 33 | // better caching in the app, we should return the 34 | // new row 35 | return newValues 36 | } 37 | 38 | // Nothing affected 39 | return null 40 | } 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/BE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/QA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/AE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/DE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/HU.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/ML.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/SL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/AM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/BG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/BO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/CI.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/GA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/IM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/LT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/TD.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/TO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /static/assets/country-flags/LB.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /static/assets/country-flags/PS.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/ES.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/GN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /static/assets/country-flags/AT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/CL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/GM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/MV.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/BS.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /static/assets/country-flags/CR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /static/assets/country-flags/TH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /resolvers/addManualPerson.js: -------------------------------------------------------------------------------- 1 | import Raven from 'raven' 2 | 3 | import { ManualPerson } from '../models' 4 | import getTzAndLoc from '../helpers/google/getTzAndLoc' 5 | import followingList from './followingList' 6 | 7 | export default async ( 8 | obj, 9 | { 10 | firstName, 11 | lastName, 12 | twitterHandle, 13 | placeId, 14 | timezone: inputTimezone, 15 | photoUrl, 16 | photoCloudObject, 17 | }, 18 | ctx, 19 | info, 20 | ) => { 21 | // Find timezone and exact location based on placeId 22 | let { city, fullLocation, timezone } = await getTzAndLoc(placeId) 23 | 24 | // For UTC 25 | if (!placeId && !city && inputTimezone) { 26 | city = null 27 | fullLocation = null 28 | timezone = inputTimezone 29 | } 30 | // Create and save place 31 | const savedPerson = await ManualPerson.create({ 32 | firstName, 33 | lastName, 34 | twitterHandle, 35 | photoUrl, 36 | photoCloudObject, 37 | 38 | city, 39 | fullLocation, 40 | timezone, 41 | }) 42 | 43 | try { 44 | await ctx.user.addManualPerson(savedPerson) 45 | } catch (err) { 46 | Raven.captureException(err) 47 | console.log(err) 48 | return err 49 | } 50 | 51 | return savedPerson.get({ plain: true }) 52 | } 53 | -------------------------------------------------------------------------------- /static/assets/country-flags/RO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /static/assets/country-flags/CG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/NE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /resolvers/allUsersByName.js: -------------------------------------------------------------------------------- 1 | import { flag } from 'country-emoji' 2 | import { Op } from 'sequelize' 3 | import { User } from '../models' 4 | 5 | // Utilities 6 | import { types as locationPolicyTypes } from '../helpers/privacy/showLocationPolicy' 7 | 8 | export default async (obj, args, ctx) => { 9 | const users = await User.findAll({ 10 | where: { 11 | [Op.or]: { 12 | fullName: { [Op.like]: `%${args.name.split(' ').join('%')}%` }, 13 | twitterHandle: { [Op.like]: `%${args.name.replace('@', '')}%` }, 14 | }, 15 | }, 16 | limit: args.limit || undefined, 17 | }) 18 | 19 | return users.map(wrappedUser => { 20 | const user = wrappedUser.get({ plain: true }) 21 | 22 | // Add the flag 23 | if (user.showLocationPolicy === locationPolicyTypes.NEVER) { 24 | user.countryFlag = '☁️' 25 | } else if (user.fullLocation) { 26 | const locationParts = user.fullLocation.split(',') 27 | const countryName = locationParts[locationParts.length - 1] 28 | user.countryFlag = flag(countryName) 29 | } else { 30 | user.countryFlag = '' 31 | } 32 | 33 | // Privacy! 34 | delete user.city 35 | delete user.fullLocation 36 | delete user.email 37 | delete user.twitterId 38 | return user 39 | }) 40 | } 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/MR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /static/assets/country-flags/LA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/TL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/JM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /static/assets/country-flags/MU.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /static/assets/country-flags/KW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /static/assets/country-flags/SD.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/LI.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/TZ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /static/assets/country-flags/LS.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/SI.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/TR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /resolvers/sortFollowings.js: -------------------------------------------------------------------------------- 1 | import { FollowingsOrder } from '../models' 2 | import followingList from './followingList' 3 | 4 | export default async (obj, { peopleIds, placesIds }, ctx, info) => { 5 | const values = {} 6 | 7 | if (peopleIds) { 8 | values.peopleIds = JSON.stringify(peopleIds) 9 | } 10 | if (placesIds) { 11 | values.placesIds = JSON.stringify(placesIds) 12 | } 13 | 14 | let newFollowingsOrder 15 | 16 | // Check if we currently have an order record 17 | const currentOrder = await ctx.user.getFollowingsOrder() 18 | 19 | if (currentOrder) { 20 | // Save it before updating, since .update, mutates 21 | // `currentOrder` data! 22 | const futureFollowingsOrder = { 23 | peopleIds: peopleIds || currentOrder.get('peopleIds'), 24 | placesIds: placesIds || currentOrder.get('placesIds'), 25 | } 26 | // Update the current order 27 | await currentOrder.update(values) 28 | // Set reserved data to pass to followingList resolver 29 | newFollowingsOrder = futureFollowingsOrder 30 | } else { 31 | // Create a new order record! 32 | const followingsOrder = FollowingsOrder.build(values) 33 | await ctx.user.setFollowingsOrder(followingsOrder) 34 | newFollowingsOrder = followingsOrder.get({ plain: true }) 35 | } 36 | 37 | return await followingList(null, null, ctx, info, newFollowingsOrder) 38 | } 39 | -------------------------------------------------------------------------------- /static/assets/country-flags/CN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 9 | 10 | 11 | 13 | 15 | 17 | 19 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /static/assets/country-flags/DK.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /static/assets/country-flags/GH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/IN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /static/assets/country-flags/MW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/SE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 10 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /static/assets/country-flags/MA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/NF.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /static/assets/country-flags/MM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/AR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /static/assets/country-flags/CM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /static/assets/country-flags/DJ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /static/assets/country-flags/EG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /static/assets/country-flags/CA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /static/assets/country-flags/PA.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | -------------------------------------------------------------------------------- /static/assets/country-flags/SN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /helpers/github.js: -------------------------------------------------------------------------------- 1 | import fetch from 'node-fetch' 2 | import Lru from 'lru-cache' 3 | import ms from 'ms' 4 | 5 | const debug = require('debug')('github') 6 | const { GH_TOKEN_RELEASES } = process.env 7 | 8 | // Cache 9 | const latestReleaseAssetsKey = 'github-latest-release' 10 | const cache = Lru({ 11 | max: 10, 12 | maxAge: ms('5m'), 13 | }) 14 | 15 | export const getLatestReleaseDlLink = async () => { 16 | const cachedAssets = cache.get(latestReleaseAssetsKey) 17 | 18 | let assets 19 | if (!cachedAssets) { 20 | debug('Fetching latest release...') 21 | 22 | // We should fetch for the first time 23 | const response = await fetch( 24 | 'https://api.github.com/repos/therepm/there-desktop/releases/latest', 25 | { headers: { Authorization: `token ${GH_TOKEN_RELEASES}` } }, 26 | ) 27 | ;({ assets } = await response.json()) 28 | cache.set(latestReleaseAssetsKey, assets) 29 | } else { 30 | debug('Using cached latest release assets') 31 | 32 | // The assets have been cached 33 | assets = cachedAssets 34 | } 35 | 36 | // Check if there is no assets, return an error 37 | if (!assets) { 38 | debug(`No assets were found.`) 39 | throw new Error( 40 | `Sorry, couldn't find the file now. Chat with us on https://there.team`, 41 | ) 42 | } 43 | 44 | const asset = assets.find(asset => asset.name.endsWith('-mac.zip')) 45 | return asset.browser_download_url 46 | } 47 | -------------------------------------------------------------------------------- /static/assets/country-flags/TN.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/SC.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /static/assets/country-flags/BR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /static/assets/country-flags/PT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 10 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/SY.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/CX.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /static/assets/country-flags/HT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /static/assets/country-flags/NR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/JO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/ZM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /static/assets/country-flags/BW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 10 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /static/assets/country-flags/GW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /static/assets/country-flags/SR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 8 | 9 | 11 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /static/assets/country-flags/DZ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/AL.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /static/assets/country-flags/GG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /static/assets/country-flags/KP.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /static/assets/country-flags/VC.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/TG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /static/assets/country-flags/GR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /static/assets/country-flags/KH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/RW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /helpers/auth/jwt.js: -------------------------------------------------------------------------------- 1 | import { Strategy as JwtStrategy, ExtractJwt } from 'passport-jwt' 2 | import { 3 | encode as jwtSimpleEncode, 4 | decode as jwtSimpleDecode, 5 | } from 'jwt-simple' 6 | import Raven from 'raven' 7 | 8 | const { JWT_SECRET } = process.env 9 | 10 | export const jwtStrategy = new JwtStrategy( 11 | { 12 | jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(), 13 | secretOrKey: JWT_SECRET, 14 | ignoreExpiration: true, 15 | passReqToCallback: true, 16 | }, 17 | (req, jwtPayload, done) => { 18 | req.userId = jwtPayload.userId 19 | return done(null, { id: jwtPayload.userId }) 20 | }, 21 | ) 22 | 23 | export const encodeJwt = userId => jwtSimpleEncode({ userId }, JWT_SECRET) 24 | 25 | export const parseUserIdIfAuthorized = (req, res, next) => { 26 | const { authorization } = req.headers 27 | 28 | // If request is not authorized, skip! 29 | if ( 30 | !authorization || 31 | !authorization.includes('Bearer') || 32 | authorization.toLowerCase().includes('null') 33 | ) { 34 | next() 35 | return 36 | } 37 | 38 | try { 39 | const token = authorization.trim().split(' ')[1] 40 | 41 | if (token.split('.').length !== 3) { 42 | // Token is invalid 43 | // https://stackoverflow.com/a/38712298/4726475 44 | next() 45 | } 46 | 47 | const payload = jwtSimpleDecode(token, JWT_SECRET) 48 | 49 | if (payload && payload.userId) { 50 | req.userId = payload.userId 51 | } 52 | } catch (err) { 53 | Raven.captureException(err) 54 | } finally { 55 | next() 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /static/assets/country-flags/MX.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /static/assets/country-flags/PR.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 11 | 12 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /static/assets/country-flags/TT.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 10 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /utils/randomPhrase.js: -------------------------------------------------------------------------------- 1 | const firstAdj = [ 2 | 'blank', 3 | 'fewer', 4 | 'kind', 5 | 'one', 6 | 'lovely', 7 | 'Popular', 8 | 'Damanded', 9 | 'final', 10 | 'useful', 11 | 'rubbed', 12 | 'plenty', 13 | 'huge', 14 | 'fine', 15 | 'nice', 16 | 'free', 17 | 'friendly', 18 | 'cool', 19 | 'Peaceful', 20 | ] 21 | 22 | const secondAdj = [ 23 | 'blank', 24 | 'calm', 25 | 'safe', 26 | 'private', 27 | 'red', 28 | 'pink', 29 | 'blue', 30 | 'missing', 31 | 'national', 32 | 'perfect', 33 | 'nighty', 34 | 'loose', 35 | 'tight', 36 | 'baggy', 37 | 'huge', 38 | 'flat', 39 | 'fast', 40 | 'mysterious', 41 | ] 42 | 43 | const names = [ 44 | 'boat', 45 | 'birds', 46 | 'bridge', 47 | 'building', 48 | 'cake', 49 | 'cheese', 50 | 'clock', 51 | 'classroom', 52 | 'flower', 53 | 'freedom', 54 | 'war', 55 | 'wealth', 56 | 'wall', 57 | 'wolf', 58 | 'poem', 59 | 'object', 60 | 'ocean', 61 | 'muscle', 62 | 'mirror', 63 | 'nose', 64 | 'nuts', 65 | 'month', 66 | 'police', 67 | 'shadow', 68 | 'stone', 69 | 'stage', 70 | 'corn', 71 | 'feathers', 72 | 'environment', 73 | 'iron', 74 | 'lamp', 75 | 'land', 76 | ] 77 | 78 | export const generateRandomPhrase = () => { 79 | const phraseWords = [firstAdj, secondAdj, names].map(collection => { 80 | const randomIndex = Math.floor(Math.random() * collection.length) 81 | 82 | const [firstChar, ...restOfWord] = collection[randomIndex] 83 | 84 | return firstChar.toUpperCase() + restOfWord.join('') 85 | }) 86 | 87 | return phraseWords.join(' ') 88 | } 89 | -------------------------------------------------------------------------------- /static/assets/country-flags/KZ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 9 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/AW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 12 | 13 | 15 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /static/assets/country-flags/OM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 11 | 12 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /static/assets/country-flags/PY.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/PF.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /static/assets/country-flags/ST.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 11 | 12 | 14 | 16 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | -------------------------------------------------------------------------------- /static/assets/country-flags/AG.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /static/assets/country-flags/FM.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 9 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /static/assets/country-flags/JE.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 11 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /static/assets/country-flags/TW.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 11 | 12 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/BB.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 11 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /static/assets/country-flags/SV.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /static/assets/country-flags/MH.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 10 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/AF.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 7 | 8 | 9 | 10 | 13 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /static/assets/country-flags/FO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 11 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /static/assets/country-flags/IS.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /static/assets/country-flags/NI.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 9 | 10 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /static/assets/country-flags/NO.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 6 | 7 | 8 | 10 | 12 | 13 | 14 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | -------------------------------------------------------------------------------- /static/assets/country-flags/BZ.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | 8 | 9 | 11 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | --------------------------------------------------------------------------------