Skip to content

Commit c657997

Browse files
committedSep 15, 2018
Begin rewrite from GraphQL to REST based API
Special thanks to @javieraviles for the node-typescript-koa-rest boilerplate https://github.com/javieraviles/node-typescript-koa-rest
1 parent 5612660 commit c657997

34 files changed

+721
-1252
lines changed
 

‎.example.env

+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
PORT=3000
2+
NODE_ENV=development
3+
JWT_SECRET=your-secret-whatever
4+
DATABASE__URL=postgres://postgres:mysecretpw@localhost:5432/postgres

‎.gitignore

+14-55
Original file line numberDiff line numberDiff line change
@@ -1,63 +1,22 @@
11
/src/config/google/jwt.keys.json
22

3-
# Logs
4-
logs
5-
*.log
6-
npm-debug.log*
7-
yarn-debug.log*
8-
yarn-error.log*
9-
10-
# Runtime data
11-
pids
12-
*.pid
13-
*.seed
14-
*.pid.lock
15-
16-
# Directory for instrumented libs generated by jscoverage/JSCover
17-
lib-cov
18-
19-
# Coverage directory used by tools like istanbul
20-
coverage
21-
22-
# nyc test coverage
23-
.nyc_output
24-
25-
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
26-
.grunt
3+
# API keys and secrets
4+
.env
275

28-
# Bower dependency directory (https://bower.io/)
6+
# Dependency directory
7+
node_modules
298
bower_components
309

31-
# node-waf configuration
32-
.lock-wscript
33-
34-
# Compiled binary addons (https://nodejs.org/api/addons.html)
35-
build/Release
10+
# Editors
11+
.idea
12+
*.iml
3613

37-
# Dependency directories
38-
node_modules/
39-
jspm_packages/
14+
# OS metadata
15+
.DS_Store
16+
Thumbs.db
4017

41-
# TypeScript v1 declaration files
42-
typings/
43-
44-
# Optional npm cache directory
45-
.npm
46-
47-
# Optional eslint cache
48-
.eslintcache
49-
50-
# Optional REPL history
51-
.node_repl_history
52-
53-
# Output of 'npm pack'
54-
*.tgz
55-
56-
# Yarn Integrity file
57-
.yarn-integrity
58-
59-
# dotenv environment variables file
60-
.env
18+
# Ignore built ts files
19+
dist/**/*
6120

62-
# next.js build output
63-
.next
21+
# Logging files
22+
*.log

‎.travis.yml

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"language": "node_js",
3+
"node_js": "8",
4+
"script": [
5+
"npm run build",
6+
"npm run test"
7+
]
8+
}

‎nodemon.json

-5
This file was deleted.

‎package.json

+44-15
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,65 @@
11
{
2+
"name": "podverse-api",
3+
"version": "1.0.0",
4+
"description": "Data API, database migration scripts, and backend services for all Podverse models.",
5+
"main": "dist/server.js",
26
"scripts": {
37
"addAllFeedsToQueue": "ts-node -r tsconfig-paths/register ./src/scripts/queue/addAllFeedsToQueue.ts",
48
"parseFeed": "ts-node -r tsconfig-paths/register ./src/scripts/parser/parseFeed.ts",
59
"parseAllFeedsFromQueue": "ts-node -r tsconfig-paths/register ./src/scripts/parser/parseAllFeedsFromQueue.ts",
610
"queryUniquePageviews": "ts-node -r tsconfig-paths/register ./src/scripts/stats/queryUniquePageviews.ts",
711
"receiveNextFeedFromQueue": "ts-node -r tsconfig-paths/register ./src/scripts/queue/receiveNextFeedFromQueue.ts",
8-
"start": "nodemon"
12+
"watch-server": "nodemon --watch 'src/**/*' -e ts,tsx --exec ts-node -r tsconfig-paths/register src/server.ts"
913
},
10-
"dependencies": {
14+
"license": "AGPLv3",
15+
"homepage": "https://github.com/podverse/podverse-api#readme",
16+
"keywords": [
17+
"podcast",
18+
"episode",
19+
"RSS",
20+
"Atom",
21+
"feed",
22+
"parser",
23+
"feedparser",
24+
"database",
25+
"db",
26+
"sequelize",
27+
"podverse"
28+
],
29+
"devDependencies": {
30+
"@types/dotenv": "^4.0.3",
1131
"@types/google.analytics": "^0.0.39",
32+
"@types/koa": "^2.0.46",
33+
"@types/koa-bodyparser": "^5.0.1",
34+
"@types/koa-helmet": "^3.1.2",
35+
"@types/koa-jwt": "^3.3.0",
36+
"@types/koa-router": "^7.0.31",
37+
"@types/koa__cors": "^2.2.3",
1238
"@types/node": "^10.9.4",
13-
"apollo-errors": "^1.9.0",
14-
"apollo-server-koa": "^2.0.5",
15-
"aws-sdk": "^2.311.0",
39+
"@types/shelljs": "^0.8.0",
40+
"nodemon": "^1.18.4",
41+
"ts-node": "^7.0.1",
42+
"tslint": "^5.11.0",
43+
"tslint-config-standard": "^8.0.1",
44+
"typescript": "^3.0.3"
45+
},
46+
"dependencies": {
47+
"@koa/cors": "^2.2.2",
48+
"aws-sdk": "^2.315.0",
1649
"class-validator": "^0.9.1",
1750
"googleapis": "^33.0.0",
18-
"graphql": "^14.0.0",
19-
"koa": "^2.5.2",
51+
"koa": "^2.5.3",
2052
"koa-bodyparser": "^4.2.1",
53+
"koa-helmet": "^4.0.0",
54+
"koa-jwt": "^3.5.1",
2155
"koa-router": "^7.4.0",
22-
"merge-graphql-schemas": "^1.5.3",
2356
"node-podcast-parser": "^2.3.0",
24-
"nodemon": "^1.18.4",
2557
"pg": "^7.4.3",
26-
"reflect-metadata": "^0.1.12",
58+
"pg-connection-string": "^2.0.0",
2759
"request": "^2.88.0",
2860
"shortid": "^2.2.13",
29-
"ts-node": "^7.0.1",
30-
"tsconfig-paths": "^3.5.0",
31-
"tslint": "^5.11.0",
32-
"tslint-config-standard": "^8.0.1",
61+
"tsconfig-paths": "^3.6.0",
3362
"typeorm": "^0.2.7",
34-
"typescript": "^3.0.3"
63+
"winston": "^3.1.0"
3564
}
3665
}

‎src/config/index.ts

+28-24
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,35 @@
1-
import { ConnectionOptions } from 'typeorm'
1+
import * as dotenv from 'dotenv'
22

3-
const env = process.env
3+
dotenv.config({ path: '.env' })
44

5-
export const awsConfig = {
6-
queueUrls: {
7-
feedsToParse: env.AWS_QUEUE_FEED_PARSER_URL,
8-
feedsToParseErrors: env.AWS_QUEUE_FEED_PARSER_ERRORS_URL
9-
},
10-
region: env.AWS_REGION
5+
interface EntityRelationships {
6+
mediaRef: {
7+
mustHavePodcast: boolean
8+
mustHaveUser: boolean
9+
}
1110
}
1211

13-
export const dbConfig = {
14-
database: 'postgres',
15-
host: env.DB_HOST || '0.0.0.0',
16-
log: {
17-
query: env.DB_LOG_QUERIES || true
18-
},
19-
password: env.DB_PASSWORD || 'mysecretpw',
20-
port: env.DB_PORT || 5432,
21-
synchronize: env.DB_SYNCHRONIZE || true,
22-
type: 'postgres',
23-
username: env.DB_USERNAME || 'postgres'
24-
} as ConnectionOptions
12+
export interface IConfig {
13+
port: number
14+
debugLogging: boolean
15+
dbsslconn: boolean
16+
jwtSecret: string
17+
databaseUrl: string
18+
entityRelationships: EntityRelationships
19+
}
2520

26-
export const entityRelationships = {
27-
mediaRef: {
28-
mustHavePodcast: env.MEDIA_REF_HAS_PODCAST || false,
29-
mustHaveUser: env.MEDIA_REF_HAS_USER || false
21+
const config: IConfig = {
22+
port: +process.env.PORT || 3000,
23+
debugLogging: process.env.NODE_ENV === 'development',
24+
dbsslconn: process.env.NODE_ENV !== 'development',
25+
jwtSecret: process.env.JWT_SECRET || 'your-secret-whatever',
26+
databaseUrl: process.env.DATABASE_URL || 'postgres://postgres:mysecretpw@localhost:5432/postgres',
27+
entityRelationships: {
28+
mediaRef: {
29+
mustHavePodcast: process.env.MEDIA_REF_HAS_PODCAST === 'true',
30+
mustHaveUser: process.env.MEDIA_REF_HAS_USER === 'true'
31+
}
3032
}
3133
}
34+
35+
export { config }

‎src/controller/general.ts

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
import { BaseContext } from 'koa'
2+
3+
export default class GeneralController {
4+
5+
public static async helloWorld (ctx: BaseContext) {
6+
ctx.body = 'Hellloooo world!'
7+
}
8+
9+
}

‎src/controller/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
export { default as general } from './general'

‎src/entities/mediaRef.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,8 @@ import { BeforeInsert, Column, CreateDateColumn, Entity, JoinColumn,
44
BeforeUpdate } from 'typeorm'
55
import { Author, Category, Episode, Playlist, Podcast, User } from 'entities'
66

7-
import { entityRelationships } from 'config'
7+
import { config } from 'config'
8+
const { entityRelationships } = config
89
const { mustHavePodcast, mustHaveUser } = entityRelationships.mediaRef
910

1011
const shortid = require('shortid')

‎src/graphql/resolvers/author.ts

-19
This file was deleted.

‎src/graphql/resolvers/category.ts

-19
This file was deleted.

‎src/graphql/resolvers/episode.ts

-19
This file was deleted.

‎src/graphql/resolvers/feedUrl.ts

-19
This file was deleted.

‎src/graphql/resolvers/index.ts

-6
This file was deleted.

‎src/graphql/resolvers/mediaRef.ts

-41
This file was deleted.

‎src/graphql/resolvers/playlist.ts

-41
This file was deleted.

‎src/graphql/resolvers/podcast.ts

-19
This file was deleted.

‎src/graphql/resolvers/user.ts

-43
This file was deleted.

‎src/graphql/types/author.ts

-17
This file was deleted.

‎src/graphql/types/category.ts

-19
This file was deleted.

‎src/graphql/types/episode.ts

-35
This file was deleted.

‎src/graphql/types/feedUrl.ts

-14
This file was deleted.

‎src/graphql/types/index.ts

-6
This file was deleted.

‎src/graphql/types/mediaRef.ts

-81
This file was deleted.

‎src/graphql/types/playlist.ts

-48
This file was deleted.

‎src/graphql/types/podcast.ts

-31
This file was deleted.

‎src/graphql/types/user.ts

-43
This file was deleted.

‎src/initializers/database.ts

+22-5
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
1+
import * as PostgressConnectionStringParser from 'pg-connection-string'
12
import { createConnection, ConnectionOptions } from 'typeorm'
2-
import { dbConfig } from 'config'
3+
import { config } from 'config'
34
import { Author, Category, Episode, FeedUrl, MediaRef, Playlist,
45
Podcast, User } from 'entities'
56
import seedDatabase from 'initializers/seedDatabase'
@@ -17,13 +18,29 @@ export const databaseInitializer = async () => {
1718
User
1819
]
1920

21+
const connectionOptions = PostgressConnectionStringParser.parse(config.databaseUrl)
22+
2023
const options: ConnectionOptions = {
21-
...dbConfig,
22-
entities
24+
type: 'postgres',
25+
host: connectionOptions.host,
26+
port: connectionOptions.port,
27+
username: connectionOptions.user,
28+
password: connectionOptions.password,
29+
database: connectionOptions.database,
30+
synchronize: true,
31+
logging: false,
32+
entities,
33+
extra: {
34+
ssl: config.dbsslconn // if not development, will use SSL
35+
}
2336
}
2437

25-
let connection = await createConnection(options)
38+
const connection = await createConnection(options)
39+
.then(connection => connection)
40+
.catch(error => console.log('TypeORM connection error: ', error))
2641

27-
// await seedDatabase(connection)
42+
if (connection) {
43+
await seedDatabase(connection)
44+
}
2845

2946
}

‎src/initializers/seedDatabase.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
import { validCategories } from 'config'
1+
import { Connection } from 'typeorm'
2+
import { validCategories } from 'config/categories'
23
import { Author, Category, Episode, FeedUrl, MediaRef, Playlist,
34
Podcast, User } from 'entities'
45
import { parseFeed } from 'services/parser'
56

6-
export default async connection => {
7+
export default async (connection: Connection) => {
78
await connection.synchronize(true)
89

910
let podcast1 = new Podcast()
@@ -136,8 +137,13 @@ export default async connection => {
136137
await parseFeeds()
137138
}
138139

139-
const generateCategories = async (connection, data, parent, shouldSave) => {
140-
let newCategories = []
140+
const generateCategories = async (
141+
connection: Connection,
142+
data: any,
143+
parent: any,
144+
shouldSave: boolean
145+
): Promise<any> => {
146+
let newCategories: any[] = []
141147
for (let category of data) {
142148
category.parent = parent
143149
let c = generateCategory(category)
@@ -153,12 +159,12 @@ const generateCategories = async (connection, data, parent, shouldSave) => {
153159

154160
if (shouldSave) {
155161
await connection.manager.save(newCategories)
156-
} else {
157-
return newCategories
158162
}
163+
164+
return newCategories
159165
}
160166

161-
const generateCategory = data => {
167+
const generateCategory = (data: any) => {
162168
let category = new Category()
163169
category.title = data.title
164170
category.category = data.parent

‎src/logging.ts

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
import * as Koa from 'koa'
2+
import * as winston from 'winston'
3+
import { config } from './config'
4+
5+
// TODO: does this have to be type "any"?
6+
export function logger (winstonInstance: any) {
7+
8+
return async (ctx: Koa.Context, next: () => Promise<any>) => {
9+
const start = new Date().getMilliseconds()
10+
11+
await next()
12+
13+
const ms = new Date().getMilliseconds() - start
14+
15+
let logLevel: string
16+
if (ctx.status >= 500) {
17+
logLevel = 'error'
18+
}
19+
if (ctx.status >= 400) {
20+
logLevel = 'warn'
21+
}
22+
if (ctx.status >= 100) {
23+
logLevel = 'info'
24+
}
25+
26+
const msg: string = `${ctx.method} ${ctx.originalUrl} ${ctx.status} ${ms}ms`
27+
28+
winstonInstance.configure({
29+
level: config.debugLogging ? 'debug' : 'info',
30+
transports: [
31+
new winston.transports.File({ filename: 'error.log', level: 'error' }),
32+
new winston.transports.Console({ format: winston.format.combine(
33+
winston.format.colorize(),
34+
winston.format.simple()
35+
)})
36+
]
37+
})
38+
39+
winstonInstance.log(logLevel, msg)
40+
}
41+
42+
}

‎src/routes.ts

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
import * as Router from 'koa-router'
2+
const controller = require('./controller')
3+
4+
const router = new Router()
5+
6+
router.get('/', controller.general.helloWorld)
7+
8+
export { router }

‎src/server.ts

+21-8
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,32 @@
1-
import { ApolloServer } from 'apollo-server-koa'
1+
import * as cors from '@koa/cors'
2+
import * as dotenv from 'dotenv'
23
import * as Koa from 'koa'
3-
import resolvers from 'graphql/resolvers'
4-
import typeDefs from 'graphql/types'
4+
import * as bodyParser from 'koa-bodyparser'
5+
import * as helmet from 'koa-helmet'
6+
import * as jwt from 'koa-jwt'
7+
import * as winston from 'winston'
8+
9+
import { config } from 'config'
10+
import { logger } from 'logging'
11+
import { router } from 'routes'
512
import { databaseInitializer } from 'initializers/database'
613

714
const bootstrap = async () => {
15+
dotenv.config({ path: './env' })
16+
817
await databaseInitializer()
918

10-
const server = new ApolloServer({ typeDefs, resolvers })
1119
const app = new Koa()
12-
server.applyMiddleware({ app })
1320

14-
app.listen({ port: 2001 }, () => {
15-
console.log(`🚀 Server ready at http://localhost:2001${server.graphqlPath}`)
16-
})
21+
app.use(helmet())
22+
app.use(cors())
23+
app.use(logger(winston))
24+
app.use(bodyParser())
25+
// app.use(jwt({ secret: config.jwtSecret }))
26+
app.use(router.routes()).use(router.allowedMethods())
27+
app.listen(config.port)
28+
29+
console.log(`Server running on port ${config.port}`)
1730
}
1831

1932
bootstrap()

‎tsconfig.json

+51-8
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,62 @@
11
{
22
"compileOnSave": false,
33
"compilerOptions": {
4-
"noImplicitAny": false,
5-
"emitDecoratorMetadata": true,
6-
"experimentalDecorators": true,
7-
"moduleResolution": "node",
4+
/* Basic Options */
5+
"target": "ES2017", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017','ES2018' or 'ESNEXT'. */
6+
// "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
87
"lib": [
98
"es2017"
10-
],
9+
], /* Specify library files to be included in the compilation. */
10+
// "allowJs": true, /* Allow javascript files to be compiled. */
11+
// "checkJs": true, /* Report errors in .js files. */
12+
// "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
13+
// "declaration": true, /* Generates corresponding '.d.ts' file. */
14+
"sourceMap": true, /* Generates corresponding '.map' file. */
15+
// "outFile": "./", /* Concatenate and emit output to single file. */
16+
"outDir": "dist", /* Redirect output structure to the directory. */
17+
// "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
18+
// "removeComments": true, /* Do not emit comments to output. */
19+
// "noEmit": true, /* Do not emit outputs. */
20+
// "importHelpers": true, /* Import emit helpers from 'tslib'. */
21+
// "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
22+
// "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */
23+
/* Strict Type-Checking Options */
24+
// "strict": true, /* Enable all strict type-checking options. */
25+
"noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */
26+
// "strictNullChecks": true, /* Enable strict null checks. */
27+
// "strictFunctionTypes": true, /* Enable strict checking of function types. */
28+
// "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */
29+
// "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */
30+
// "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */
31+
/* Additional Checks */
32+
// "noUnusedLocals": true, /* Report errors on unused locals. */
33+
// "noUnusedParameters": true, /* Report errors on unused parameters. */
34+
// "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */
35+
// "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */
36+
/* Module Resolution Options */
37+
"moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
38+
"baseUrl": "./src", /* Base directory to resolve non-absolute module names. */
39+
// "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
40+
// "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */
41+
// "typeRoots": [], /* List of folders to include type definitions from. */
1142
"types": [
1243
"node"
13-
],
14-
"sourceMap": true,
15-
"baseUrl": "./src"
44+
], /* Type declaration files to be included in compilation. */
45+
// "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
46+
// "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */
47+
// "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */
48+
/* Source Map Options */
49+
// "sourceRoot": "./", /* Specify the location where debugger should locate TypeScript files instead of source locations. */
50+
// "mapRoot": "./", /* Specify the location where debugger should locate map files instead of generated locations. */
51+
// "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */
52+
// "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */
53+
/* Experimental Options */
54+
"experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */
55+
"emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */
1656
},
57+
"include": [
58+
"src/**/*"
59+
],
1760
"exclude": [
1861
"node_modules",
1962
"**/*.spec.ts"

‎yarn.lock

+454-604
Large diffs are not rendered by default.

0 commit comments

Comments
 (0)
Please sign in to comment.