Compare commits
	
		
			62 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 138fd4a121 | |||
| 5648b8fffa | |||
| 1b7a754224 | |||
| 5d91f1bae7 | |||
| cdcfae56e7 | |||
| f2c77e092d | |||
| d6ac7e55e9 | |||
| a3543090f2 | |||
| 041e944783 | |||
| bfd31ebd23 | |||
| 5036f4ca36 | |||
| 61b59ae3ea | |||
| 92c49ac523 | |||
| f680642f25 | |||
| f89486ae9e | |||
| 4d853565d1 | |||
| 91c81e5cf6 | |||
| 0ecbde9675 | |||
| d8e951c2ef | |||
| 90f3d86511 | |||
| c6791a7027 | |||
| 5b6a2c2651 | |||
| 4f7a22fff1 | |||
| 31b0c998a8 | |||
| 9ce5b476ef | |||
| 554f292e4c | |||
| d8985aaff7 | |||
| be889b8100 | |||
| b5bd672f44 | |||
| 4501bc5302 | |||
| b384e748af | |||
| c676f182b4 | |||
| 95d2b0095b | |||
| 8165cf8e85 | |||
| 14775744b0 | |||
| 559e32c059 | |||
| f4dbaf4c58 | |||
| 1d25914ae0 | |||
| 4d3d8c874c | |||
| 08433523b7 | |||
| fce8879994 | |||
| 505b126043 | |||
| 589bd7b08d | |||
| f0049ffb4e | |||
| 2b25397253 | |||
| 776f83553a | |||
| 815aaedffb | |||
| 578eff30fb | |||
| 943cbe5cb8 | |||
| f89db46bf2 | |||
| 085fb76e11 | |||
| aa4a1c2a57 | |||
| 74340afd16 | |||
| 2672266908 | |||
| f37786aa76 | |||
| 91f64e5cfb | |||
| a4d3123910 | |||
| bc6fe3ed48 | |||
| b23566509f | |||
| 341a07621d | |||
| 259ed9b06f | |||
| cddf06cbcc | 
							
								
								
									
										10
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								.prettierrc
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  "tabWidth": 2,
 | 
				
			||||||
 | 
					  "useTabs": false,
 | 
				
			||||||
 | 
					  "semi": true,
 | 
				
			||||||
 | 
					  "singleQuote": false,
 | 
				
			||||||
 | 
					  "bracketSpacing": true,
 | 
				
			||||||
 | 
					  "arrowParens": "avoid",
 | 
				
			||||||
 | 
					  "vueIndentScriptAndStyle": false,
 | 
				
			||||||
 | 
					  "trailingComma": "none"
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										1263
									
								
								.spectral.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1263
									
								
								.spectral.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										38
									
								
								.stoplight/custom-functions/oasDiscriminator.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								.stoplight/custom-functions/oasDiscriminator.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,38 @@
 | 
				
			|||||||
 | 
					function isObject(value) {
 | 
				
			||||||
 | 
					  return value !== null && typeof value === 'object';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const oasDiscriminator = (schema, _opts, { path }) => {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * This function verifies:
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * 1. The discriminator property name is defined at this schema.
 | 
				
			||||||
 | 
					   * 2. The discriminator property is in the required property list.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!isObject(schema)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (typeof schema.discriminator !== 'string') return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const discriminatorName = schema.discriminator;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const results = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!isObject(schema.properties) || !Object.keys(schema.properties).some(k => k === discriminatorName)) {
 | 
				
			||||||
 | 
					    results.push({
 | 
				
			||||||
 | 
					      message: `The discriminator property must be defined in this schema.`,
 | 
				
			||||||
 | 
					      path: [...path, 'properties'],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!Array.isArray(schema.required) || !schema.required.some(n => n === discriminatorName)) {
 | 
				
			||||||
 | 
					    results.push({
 | 
				
			||||||
 | 
					      message: `The discriminator property must be in the required property list.`,
 | 
				
			||||||
 | 
					      path: [...path, 'required'],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return results;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default oasDiscriminator;
 | 
				
			||||||
							
								
								
									
										4274
									
								
								.stoplight/custom-functions/oasDocumentSchema.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4274
									
								
								.stoplight/custom-functions/oasDocumentSchema.js
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										228
									
								
								.stoplight/custom-functions/oasExample.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										228
									
								
								.stoplight/custom-functions/oasExample.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,228 @@
 | 
				
			|||||||
 | 
					import { isPlainObject, pointerToPath } from '@stoplight/json';
 | 
				
			||||||
 | 
					import { createRulesetFunction } from '@stoplight/spectral-core';
 | 
				
			||||||
 | 
					import { oas2, oas3_1, extractDraftVersion, oas3_0 } from '@stoplight/spectral-formats';
 | 
				
			||||||
 | 
					import { schema as schemaFn } from '@stoplight/spectral-functions';
 | 
				
			||||||
 | 
					import traverse from 'json-schema-traverse';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const MEDIA_VALIDATION_ITEMS = {
 | 
				
			||||||
 | 
					  2: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      field: 'examples',
 | 
				
			||||||
 | 
					      multiple: true,
 | 
				
			||||||
 | 
					      keyed: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  3: [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      field: 'example',
 | 
				
			||||||
 | 
					      multiple: false,
 | 
				
			||||||
 | 
					      keyed: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      field: 'examples',
 | 
				
			||||||
 | 
					      multiple: true,
 | 
				
			||||||
 | 
					      keyed: true,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const SCHEMA_VALIDATION_ITEMS = {
 | 
				
			||||||
 | 
					  2: ['example', 'x-example', 'default'],
 | 
				
			||||||
 | 
					  3: ['example', 'default'],
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isObject(value) {
 | 
				
			||||||
 | 
					  return value !== null && typeof value === 'object';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function rewriteNullable(schema, errors) {
 | 
				
			||||||
 | 
					  for (const error of errors) {
 | 
				
			||||||
 | 
					    if (error.keyword !== 'type') continue;
 | 
				
			||||||
 | 
					    const value = getSchemaProperty(schema, error.schemaPath);
 | 
				
			||||||
 | 
					    if (isPlainObject(value) && value.nullable === true) {
 | 
				
			||||||
 | 
					      error.message += ',null';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const visitOAS2 = schema => {
 | 
				
			||||||
 | 
					  if (schema['x-nullable'] === true) {
 | 
				
			||||||
 | 
					    schema.nullable = true;
 | 
				
			||||||
 | 
					    delete schema['x-nullable'];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getSchemaProperty(schema, schemaPath) {
 | 
				
			||||||
 | 
					  const path = pointerToPath(schemaPath);
 | 
				
			||||||
 | 
					  let value = schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const fragment of path.slice(0, -1)) {
 | 
				
			||||||
 | 
					    if (!isPlainObject(value)) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    value = value[fragment];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return value;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const oasSchema = createRulesetFunction(
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    input: null,
 | 
				
			||||||
 | 
					    options: {
 | 
				
			||||||
 | 
					      type: 'object',
 | 
				
			||||||
 | 
					      properties: {
 | 
				
			||||||
 | 
					        schema: {
 | 
				
			||||||
 | 
					          type: 'object',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      additionalProperties: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  function oasSchema(targetVal, opts, context) {
 | 
				
			||||||
 | 
					    const formats = context.document.formats;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let { schema } = opts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let dialect = 'draft4';
 | 
				
			||||||
 | 
					    let prepareResults;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!formats) {
 | 
				
			||||||
 | 
					      dialect = 'auto';
 | 
				
			||||||
 | 
					    } else if (formats.has(oas3_1)) {
 | 
				
			||||||
 | 
					      if (isPlainObject(context.document.data) && typeof context.document.data.jsonSchemaDialect === 'string') {
 | 
				
			||||||
 | 
					        dialect = extractDraftVersion(context.document.data.jsonSchemaDialect) ?? 'draft2020-12';
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        dialect = 'draft2020-12';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else if (formats.has(oas3_0)) {
 | 
				
			||||||
 | 
					      prepareResults = rewriteNullable.bind(null, schema);
 | 
				
			||||||
 | 
					    } else if (formats.has(oas2)) {
 | 
				
			||||||
 | 
					      const clonedSchema = JSON.parse(JSON.stringify(schema));
 | 
				
			||||||
 | 
					      traverse(clonedSchema, visitOAS2);
 | 
				
			||||||
 | 
					      schema = clonedSchema;
 | 
				
			||||||
 | 
					      prepareResults = rewriteNullable.bind(null, clonedSchema);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return schemaFn(
 | 
				
			||||||
 | 
					      targetVal,
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        ...opts,
 | 
				
			||||||
 | 
					        schema,
 | 
				
			||||||
 | 
					        prepareResults,
 | 
				
			||||||
 | 
					        dialect,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      context,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function* getMediaValidationItems(items, targetVal, givenPath, oasVersion) {
 | 
				
			||||||
 | 
					  for (const { field, keyed, multiple } of items) {
 | 
				
			||||||
 | 
					    if (!(field in targetVal)) {
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const value = targetVal[field];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (multiple) {
 | 
				
			||||||
 | 
					      if (!isObject(value)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for (const exampleKey of Object.keys(value)) {
 | 
				
			||||||
 | 
					        const exampleValue = value[exampleKey];
 | 
				
			||||||
 | 
					        if (oasVersion === 3 && keyed && (!isObject(exampleValue) || 'externalValue' in exampleValue)) {
 | 
				
			||||||
 | 
					          // should be covered by oas3-examples-value-or-externalValue
 | 
				
			||||||
 | 
					          continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const targetPath = [...givenPath, field, exampleKey];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (keyed) {
 | 
				
			||||||
 | 
					          targetPath.push('value');
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        yield {
 | 
				
			||||||
 | 
					          value: keyed && isObject(exampleValue) ? exampleValue.value : exampleValue,
 | 
				
			||||||
 | 
					          path: targetPath,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      return yield {
 | 
				
			||||||
 | 
					        value,
 | 
				
			||||||
 | 
					        path: [...givenPath, field],
 | 
				
			||||||
 | 
					      };
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function* getSchemaValidationItems(fields, targetVal, givenPath) {
 | 
				
			||||||
 | 
					  for (const field of fields) {
 | 
				
			||||||
 | 
					    if (!(field in targetVal)) {
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    yield {
 | 
				
			||||||
 | 
					      value: targetVal[field],
 | 
				
			||||||
 | 
					      path: [...givenPath, field],
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default createRulesetFunction(
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    input: {
 | 
				
			||||||
 | 
					      type: 'object',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    options: {
 | 
				
			||||||
 | 
					      type: 'object',
 | 
				
			||||||
 | 
					      properties: {
 | 
				
			||||||
 | 
					        oasVersion: {
 | 
				
			||||||
 | 
					          enum: ['2', '3'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        schemaField: {
 | 
				
			||||||
 | 
					          type: 'string',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        type: {
 | 
				
			||||||
 | 
					          enum: ['media', 'schema'],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      additionalProperties: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  function oasExample(targetVal, opts, context) {
 | 
				
			||||||
 | 
					    const formats = context.document.formats;
 | 
				
			||||||
 | 
					    const schemaOpts = {
 | 
				
			||||||
 | 
					      schema: opts.schemaField === '$' ? targetVal : targetVal[opts.schemaField],
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let results = void 0;
 | 
				
			||||||
 | 
					    let oasVersion = parseInt(opts.oasVersion);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const validationItems =
 | 
				
			||||||
 | 
					      opts.type === 'schema'
 | 
				
			||||||
 | 
					        ? getSchemaValidationItems(SCHEMA_VALIDATION_ITEMS[oasVersion], targetVal, context.path)
 | 
				
			||||||
 | 
					        : getMediaValidationItems(MEDIA_VALIDATION_ITEMS[oasVersion], targetVal, context.path, oasVersion);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (formats?.has(oas2) && 'required' in schemaOpts.schema && typeof schemaOpts.schema.required === 'boolean') {
 | 
				
			||||||
 | 
					      schemaOpts.schema = { ...schemaOpts.schema };
 | 
				
			||||||
 | 
					      delete schemaOpts.schema.required;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const validationItem of validationItems) {
 | 
				
			||||||
 | 
					      const result = oasSchema(validationItem.value, schemaOpts, {
 | 
				
			||||||
 | 
					        ...context,
 | 
				
			||||||
 | 
					        path: validationItem.path,
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (Array.isArray(result)) {
 | 
				
			||||||
 | 
					        if (results === void 0) results = [];
 | 
				
			||||||
 | 
					        results.push(...result);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return results;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
							
								
								
									
										28
									
								
								.stoplight/custom-functions/oasOpFormDataConsumeCheck.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								.stoplight/custom-functions/oasOpFormDataConsumeCheck.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
				
			|||||||
 | 
					function isObject(value) {
 | 
				
			||||||
 | 
					  return value !== null && typeof value === 'object';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const validConsumeValue = /(application\/x-www-form-urlencoded|multipart\/form-data)/;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const oasOpFormDataConsumeCheck = targetVal => {
 | 
				
			||||||
 | 
					  if (!isObject(targetVal)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const parameters = targetVal.parameters;
 | 
				
			||||||
 | 
					  const consumes = targetVal.consumes;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!Array.isArray(parameters) || !Array.isArray(consumes)) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (parameters.some(p => isObject(p) && p.in === 'formData') && !validConsumeValue.test(consumes?.join(','))) {
 | 
				
			||||||
 | 
					    return [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        message: 'Consumes must include urlencoded, multipart, or form-data media type when using formData parameter.',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default oasOpFormDataConsumeCheck;
 | 
				
			||||||
							
								
								
									
										76
									
								
								.stoplight/custom-functions/oasOpIdUnique.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								.stoplight/custom-functions/oasOpIdUnique.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					import { isPlainObject } from '@stoplight/json';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isObject(value) {
 | 
				
			||||||
 | 
					  return value !== null && typeof value === 'object';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const validOperationKeys = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function* getAllOperations(paths) {
 | 
				
			||||||
 | 
					  if (!isPlainObject(paths)) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const item = {
 | 
				
			||||||
 | 
					    path: '',
 | 
				
			||||||
 | 
					    operation: '',
 | 
				
			||||||
 | 
					    value: null,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const path of Object.keys(paths)) {
 | 
				
			||||||
 | 
					    const operations = paths[path];
 | 
				
			||||||
 | 
					    if (!isPlainObject(operations)) {
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    item.path = path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const operation of Object.keys(operations)) {
 | 
				
			||||||
 | 
					      if (!isPlainObject(operations[operation]) || !validOperationKeys.includes(operation)) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      item.operation = operation;
 | 
				
			||||||
 | 
					      item.value = operations[operation];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      yield item;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const oasOpIdUnique = targetVal => {
 | 
				
			||||||
 | 
					  if (!isObject(targetVal) || !isObject(targetVal.paths)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const results = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { paths } = targetVal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const seenIds = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const { path, operation } of getAllOperations(paths)) {
 | 
				
			||||||
 | 
					    const pathValue = paths[path];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!isObject(pathValue)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const operationValue = pathValue[operation];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!isObject(operationValue) || !('operationId' in operationValue)) {
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { operationId } = operationValue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (seenIds.includes(operationId)) {
 | 
				
			||||||
 | 
					      results.push({
 | 
				
			||||||
 | 
					        message: 'operationId must be unique.',
 | 
				
			||||||
 | 
					        path: ['paths', path, operation, 'operationId'],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      seenIds.push(operationId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return results;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default oasOpIdUnique;
 | 
				
			||||||
							
								
								
									
										81
									
								
								.stoplight/custom-functions/oasOpParams.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								.stoplight/custom-functions/oasOpParams.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					function isObject(value) {
 | 
				
			||||||
 | 
					  return value !== null && typeof value === 'object';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function computeFingerprint(param) {
 | 
				
			||||||
 | 
					  return `${String(param.in)}-${String(param.name)}`;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const oasOpParams = (params, _opts, { path }) => {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * This function verifies:
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * 1. Operations must have unique `name` + `in` parameters.
 | 
				
			||||||
 | 
					   * 2. Operation cannot have both `in:body` and `in:formData` parameters
 | 
				
			||||||
 | 
					   * 3. Operation must have only one `in:body` parameter.
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!Array.isArray(params)) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (params.length < 2) return;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const results = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const count = {
 | 
				
			||||||
 | 
					    body: [],
 | 
				
			||||||
 | 
					    formData: [],
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					  const list = [];
 | 
				
			||||||
 | 
					  const duplicates = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let index = -1;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const param of params) {
 | 
				
			||||||
 | 
					    index++;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!isObject(param)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // skip params that are refs
 | 
				
			||||||
 | 
					    if ('$ref' in param) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Operations must have unique `name` + `in` parameters.
 | 
				
			||||||
 | 
					    const fingerprint = computeFingerprint(param);
 | 
				
			||||||
 | 
					    if (list.includes(fingerprint)) {
 | 
				
			||||||
 | 
					      duplicates.push(index);
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      list.push(fingerprint);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (typeof param.in === 'string' && param.in in count) {
 | 
				
			||||||
 | 
					      count[param.in].push(index);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (duplicates.length > 0) {
 | 
				
			||||||
 | 
					    for (const i of duplicates) {
 | 
				
			||||||
 | 
					      results.push({
 | 
				
			||||||
 | 
					        message: 'A parameter in this operation already exposes the same combination of "name" and "in" values.',
 | 
				
			||||||
 | 
					        path: [...path, i],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (count.body.length > 0 && count.formData.length > 0) {
 | 
				
			||||||
 | 
					    results.push({
 | 
				
			||||||
 | 
					      message: 'Operation must not have both "in:body" and "in:formData" parameters.',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (count.body.length > 1) {
 | 
				
			||||||
 | 
					    for (let i = 1; i < count.body.length; i++) {
 | 
				
			||||||
 | 
					      results.push({
 | 
				
			||||||
 | 
					        message: 'Operation must not have more than a single instance of the "in:body" parameter.',
 | 
				
			||||||
 | 
					        path: [...path, count.body[i]],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return results;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default oasOpParams;
 | 
				
			||||||
							
								
								
									
										141
									
								
								.stoplight/custom-functions/oasOpSecurityDefined.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								.stoplight/custom-functions/oasOpSecurityDefined.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,141 @@
 | 
				
			|||||||
 | 
					import { isPlainObject } from '@stoplight/json';
 | 
				
			||||||
 | 
					import { createRulesetFunction } from '@stoplight/spectral-core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isObject(value) {
 | 
				
			||||||
 | 
					  return value !== null && typeof value === 'object';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const validOperationKeys = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function* getAllOperations(paths) {
 | 
				
			||||||
 | 
					  if (!isPlainObject(paths)) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const item = {
 | 
				
			||||||
 | 
					    path: '',
 | 
				
			||||||
 | 
					    operation: '',
 | 
				
			||||||
 | 
					    value: null,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const path of Object.keys(paths)) {
 | 
				
			||||||
 | 
					    const operations = paths[path];
 | 
				
			||||||
 | 
					    if (!isPlainObject(operations)) {
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    item.path = path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const operation of Object.keys(operations)) {
 | 
				
			||||||
 | 
					      if (!isPlainObject(operations[operation]) || !validOperationKeys.includes(operation)) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      item.operation = operation;
 | 
				
			||||||
 | 
					      item.value = operations[operation];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      yield item;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function _get(value, path) {
 | 
				
			||||||
 | 
					  for (const segment of path) {
 | 
				
			||||||
 | 
					    if (!isObject(value)) {
 | 
				
			||||||
 | 
					      break;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    value = value[segment];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return value;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default createRulesetFunction(
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    input: {
 | 
				
			||||||
 | 
					      type: 'object',
 | 
				
			||||||
 | 
					      properties: {
 | 
				
			||||||
 | 
					        paths: {
 | 
				
			||||||
 | 
					          type: 'object',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        security: {
 | 
				
			||||||
 | 
					          type: 'array',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    options: {
 | 
				
			||||||
 | 
					      type: 'object',
 | 
				
			||||||
 | 
					      properties: {
 | 
				
			||||||
 | 
					        schemesPath: {
 | 
				
			||||||
 | 
					          type: 'array',
 | 
				
			||||||
 | 
					          items: {
 | 
				
			||||||
 | 
					            type: ['string', 'number'],
 | 
				
			||||||
 | 
					          },
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  function oasOpSecurityDefined(targetVal, { schemesPath }) {
 | 
				
			||||||
 | 
					    const { paths } = targetVal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const results = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const schemes = _get(targetVal, schemesPath);
 | 
				
			||||||
 | 
					    const allDefs = isObject(schemes) ? Object.keys(schemes) : [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // Check global security requirements
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { security } = targetVal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (Array.isArray(security)) {
 | 
				
			||||||
 | 
					      for (const [index, value] of security.entries()) {
 | 
				
			||||||
 | 
					        if (!isObject(value)) {
 | 
				
			||||||
 | 
					          continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const securityKeys = Object.keys(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const securityKey of securityKeys) {
 | 
				
			||||||
 | 
					          if (!allDefs.includes(securityKey)) {
 | 
				
			||||||
 | 
					            results.push({
 | 
				
			||||||
 | 
					              message: `API "security" values must match a scheme defined in the "${schemesPath.join('.')}" object.`,
 | 
				
			||||||
 | 
					              path: ['security', index, securityKey],
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const { path, operation, value } of getAllOperations(paths)) {
 | 
				
			||||||
 | 
					      if (!isObject(value)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const { security } = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (!Array.isArray(security)) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      for (const [index, value] of security.entries()) {
 | 
				
			||||||
 | 
					        if (!isObject(value)) {
 | 
				
			||||||
 | 
					          continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const securityKeys = Object.keys(value);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        for (const securityKey of securityKeys) {
 | 
				
			||||||
 | 
					          if (!allDefs.includes(securityKey)) {
 | 
				
			||||||
 | 
					            results.push({
 | 
				
			||||||
 | 
					              message: `Operation "security" values must match a scheme defined in the "${schemesPath.join(
 | 
				
			||||||
 | 
					                '.',
 | 
				
			||||||
 | 
					              )}" object.`,
 | 
				
			||||||
 | 
					              path: ['paths', path, operation, 'security', index, securityKey],
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return results;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
							
								
								
									
										32
									
								
								.stoplight/custom-functions/oasOpSuccessResponse.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								.stoplight/custom-functions/oasOpSuccessResponse.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					import { createRulesetFunction } from '@stoplight/spectral-core';
 | 
				
			||||||
 | 
					import { oas3 } from '@stoplight/spectral-formats';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const oasOpSuccessResponse = createRulesetFunction(
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    input: {
 | 
				
			||||||
 | 
					      type: 'object',
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    options: null,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  (input, opts, context) => {
 | 
				
			||||||
 | 
					    const isOAS3X = context.document.formats?.has(oas3) === true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const response of Object.keys(input)) {
 | 
				
			||||||
 | 
					      if (isOAS3X && (response === '2XX' || response === '3XX')) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (Number(response) >= 200 && Number(response) < 400) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        message: 'Operation must define at least a single 2xx or 3xx response',
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default oasOpSuccessResponse;
 | 
				
			||||||
							
								
								
									
										162
									
								
								.stoplight/custom-functions/oasPathParam.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										162
									
								
								.stoplight/custom-functions/oasPathParam.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,162 @@
 | 
				
			|||||||
 | 
					function isObject(value) {
 | 
				
			||||||
 | 
					  return value !== null && typeof value === 'object';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const pathRegex = /(\{;?\??[a-zA-Z0-9_-]+\*?\})/g;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isNamedPathParam = p => {
 | 
				
			||||||
 | 
					  return p.in !== void 0 && p.in === 'path' && p.name !== void 0;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isUnknownNamedPathParam = (p, path, results, seen) => {
 | 
				
			||||||
 | 
					  if (!isNamedPathParam(p)) {
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (p.required !== true) {
 | 
				
			||||||
 | 
					    results.push(generateResult(requiredMessage(p.name), path));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (p.name in seen) {
 | 
				
			||||||
 | 
					    results.push(generateResult(uniqueDefinitionMessage(p.name), path));
 | 
				
			||||||
 | 
					    return false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return true;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ensureAllDefinedPathParamsAreUsedInPath = (path, params, expected, results) => {
 | 
				
			||||||
 | 
					  for (const p of Object.keys(params)) {
 | 
				
			||||||
 | 
					    if (!params[p]) {
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!expected.includes(p)) {
 | 
				
			||||||
 | 
					      const resPath = params[p];
 | 
				
			||||||
 | 
					      results.push(generateResult(`Parameter "${p}" must be used in path "${path}".`, resPath));
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const ensureAllExpectedParamsInPathAreDefined = (path, params, expected, operationPath, results) => {
 | 
				
			||||||
 | 
					  for (const p of expected) {
 | 
				
			||||||
 | 
					    if (!(p in params)) {
 | 
				
			||||||
 | 
					      results.push(
 | 
				
			||||||
 | 
					        generateResult(`Operation must define parameter "{${p}}" as expected by path "${path}".`, operationPath),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const oasPathParam = targetVal => {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * This rule verifies:
 | 
				
			||||||
 | 
					   *
 | 
				
			||||||
 | 
					   * 1. for every param referenced in the path string ie /users/{userId}, var must be defined in either
 | 
				
			||||||
 | 
					   *    path.parameters, or operation.parameters object
 | 
				
			||||||
 | 
					   * 2. every path.parameters + operation.parameters property must be used in the path string
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!isObject(targetVal) || !isObject(targetVal.paths)) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const results = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // keep track of normalized paths for verifying paths are unique
 | 
				
			||||||
 | 
					  const uniquePaths = {};
 | 
				
			||||||
 | 
					  const validOperationKeys = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const path of Object.keys(targetVal.paths)) {
 | 
				
			||||||
 | 
					    const pathValue = targetVal.paths[path];
 | 
				
			||||||
 | 
					    if (!isObject(pathValue)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // verify normalized paths are functionally unique (ie `/path/{one}` vs `/path/{two}` are
 | 
				
			||||||
 | 
					    // different but equivalent within the context of OAS)
 | 
				
			||||||
 | 
					    const normalized = path.replace(pathRegex, '%'); // '%' is used here since its invalid in paths
 | 
				
			||||||
 | 
					    if (normalized in uniquePaths) {
 | 
				
			||||||
 | 
					      results.push(
 | 
				
			||||||
 | 
					        generateResult(`Paths "${String(uniquePaths[normalized])}" and "${path}" must not be equivalent.`, [
 | 
				
			||||||
 | 
					          'paths',
 | 
				
			||||||
 | 
					          path,
 | 
				
			||||||
 | 
					        ]),
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    } else {
 | 
				
			||||||
 | 
					      uniquePaths[normalized] = path;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // find all templated path parameters
 | 
				
			||||||
 | 
					    const pathElements = [];
 | 
				
			||||||
 | 
					    let match;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    while ((match = pathRegex.exec(path))) {
 | 
				
			||||||
 | 
					      const p = match[0].replace(/[{}?*;]/g, '');
 | 
				
			||||||
 | 
					      if (pathElements.includes(p)) {
 | 
				
			||||||
 | 
					        results.push(generateResult(`Path "${path}" must not use parameter "{${p}}" multiple times.`, ['paths', path]));
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        pathElements.push(p);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // find parameters set within the top-level 'parameters' object
 | 
				
			||||||
 | 
					    const topParams = {};
 | 
				
			||||||
 | 
					    if (Array.isArray(pathValue.parameters)) {
 | 
				
			||||||
 | 
					      for (const [i, value] of pathValue.parameters.entries()) {
 | 
				
			||||||
 | 
					        if (!isObject(value)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const fullParameterPath = ['paths', path, 'parameters', i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (isUnknownNamedPathParam(value, fullParameterPath, results, topParams)) {
 | 
				
			||||||
 | 
					          topParams[value.name] = fullParameterPath;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (isObject(targetVal.paths[path])) {
 | 
				
			||||||
 | 
					      // find parameters set within the operation's 'parameters' object
 | 
				
			||||||
 | 
					      for (const op of Object.keys(pathValue)) {
 | 
				
			||||||
 | 
					        const operationValue = pathValue[op];
 | 
				
			||||||
 | 
					        if (!isObject(operationValue)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (op === 'parameters' || !validOperationKeys.includes(op)) {
 | 
				
			||||||
 | 
					          continue;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const operationParams = {};
 | 
				
			||||||
 | 
					        const { parameters } = operationValue;
 | 
				
			||||||
 | 
					        const operationPath = ['paths', path, op];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        if (Array.isArray(parameters)) {
 | 
				
			||||||
 | 
					          for (const [i, p] of parameters.entries()) {
 | 
				
			||||||
 | 
					            if (!isObject(p)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            const fullParameterPath = [...operationPath, 'parameters', i];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            if (isUnknownNamedPathParam(p, fullParameterPath, results, operationParams)) {
 | 
				
			||||||
 | 
					              operationParams[p.name] = fullParameterPath;
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const definedParams = { ...topParams, ...operationParams };
 | 
				
			||||||
 | 
					        ensureAllDefinedPathParamsAreUsedInPath(path, definedParams, pathElements, results);
 | 
				
			||||||
 | 
					        ensureAllExpectedParamsInPathAreDefined(path, definedParams, pathElements, operationPath, results);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return results;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function generateResult(message, path) {
 | 
				
			||||||
 | 
					  return {
 | 
				
			||||||
 | 
					    message,
 | 
				
			||||||
 | 
					    path,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const requiredMessage = name => `Path parameter "${name}" must have "required" property that is set to "true".`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const uniqueDefinitionMessage = name => `Path parameter "${name}" must not be defined multiple times.`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default oasPathParam;
 | 
				
			||||||
							
								
								
									
										88
									
								
								.stoplight/custom-functions/oasSchema.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										88
									
								
								.stoplight/custom-functions/oasSchema.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,88 @@
 | 
				
			|||||||
 | 
					import traverse from 'json-schema-traverse';
 | 
				
			||||||
 | 
					import { schema as schemaFn } from '@stoplight/spectral-functions';
 | 
				
			||||||
 | 
					import { createRulesetFunction } from '@stoplight/spectral-core';
 | 
				
			||||||
 | 
					import { oas2, oas3_1, extractDraftVersion, oas3_0 } from '@stoplight/spectral-formats';
 | 
				
			||||||
 | 
					import { isPlainObject, pointerToPath } from '@stoplight/json';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function rewriteNullable(schema, errors) {
 | 
				
			||||||
 | 
					  for (const error of errors) {
 | 
				
			||||||
 | 
					    if (error.keyword !== 'type') continue;
 | 
				
			||||||
 | 
					    const value = getSchemaProperty(schema, error.schemaPath);
 | 
				
			||||||
 | 
					    if (isPlainObject(value) && value.nullable === true) {
 | 
				
			||||||
 | 
					      error.message += ',null';
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default createRulesetFunction(
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    input: null,
 | 
				
			||||||
 | 
					    options: {
 | 
				
			||||||
 | 
					      type: 'object',
 | 
				
			||||||
 | 
					      properties: {
 | 
				
			||||||
 | 
					        schema: {
 | 
				
			||||||
 | 
					          type: 'object',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      additionalProperties: false,
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  function oasSchema(targetVal, opts, context) {
 | 
				
			||||||
 | 
					    const formats = context.document.formats;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let { schema } = opts;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let dialect = 'draft4';
 | 
				
			||||||
 | 
					    let prepareResults;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!formats) {
 | 
				
			||||||
 | 
					      dialect = 'auto';
 | 
				
			||||||
 | 
					    } else if (formats.has(oas3_1)) {
 | 
				
			||||||
 | 
					      if (isPlainObject(context.document.data) && typeof context.document.data.jsonSchemaDialect === 'string') {
 | 
				
			||||||
 | 
					        dialect = extractDraftVersion(context.document.data.jsonSchemaDialect) ?? 'draft2020-12';
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        dialect = 'draft2020-12';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    } else if (formats.has(oas3_0)) {
 | 
				
			||||||
 | 
					      prepareResults = rewriteNullable.bind(null, schema);
 | 
				
			||||||
 | 
					    } else if (formats.has(oas2)) {
 | 
				
			||||||
 | 
					      const clonedSchema = JSON.parse(JSON.stringify(schema));
 | 
				
			||||||
 | 
					      traverse(clonedSchema, visitOAS2);
 | 
				
			||||||
 | 
					      schema = clonedSchema;
 | 
				
			||||||
 | 
					      prepareResults = rewriteNullable.bind(null, clonedSchema);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return schemaFn(
 | 
				
			||||||
 | 
					      targetVal,
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        ...opts,
 | 
				
			||||||
 | 
					        schema,
 | 
				
			||||||
 | 
					        prepareResults,
 | 
				
			||||||
 | 
					        dialect,
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      context,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const visitOAS2 = schema => {
 | 
				
			||||||
 | 
					  if (schema['x-nullable'] === true) {
 | 
				
			||||||
 | 
					    schema.nullable = true;
 | 
				
			||||||
 | 
					    delete schema['x-nullable'];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getSchemaProperty(schema, schemaPath) {
 | 
				
			||||||
 | 
					  const path = pointerToPath(schemaPath);
 | 
				
			||||||
 | 
					  let value = schema;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const fragment of path.slice(0, -1)) {
 | 
				
			||||||
 | 
					    if (!isPlainObject(value)) {
 | 
				
			||||||
 | 
					      return;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    value = value[fragment];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return value;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										81
									
								
								.stoplight/custom-functions/oasTagDefined.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								.stoplight/custom-functions/oasTagDefined.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
				
			|||||||
 | 
					// This function will check an API doc to verify that any tag that appears on
 | 
				
			||||||
 | 
					// an operation is also present in the global tags array.
 | 
				
			||||||
 | 
					import { isPlainObject } from '@stoplight/json';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isObject(value) {
 | 
				
			||||||
 | 
					  return value !== null && typeof value === 'object';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const validOperationKeys = ['get', 'head', 'post', 'put', 'patch', 'delete', 'options', 'trace'];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function* getAllOperations(paths) {
 | 
				
			||||||
 | 
					  if (!isPlainObject(paths)) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const item = {
 | 
				
			||||||
 | 
					    path: '',
 | 
				
			||||||
 | 
					    operation: '',
 | 
				
			||||||
 | 
					    value: null,
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const path of Object.keys(paths)) {
 | 
				
			||||||
 | 
					    const operations = paths[path];
 | 
				
			||||||
 | 
					    if (!isPlainObject(operations)) {
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    item.path = path;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const operation of Object.keys(operations)) {
 | 
				
			||||||
 | 
					      if (!isPlainObject(operations[operation]) || !validOperationKeys.includes(operation)) {
 | 
				
			||||||
 | 
					        continue;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      item.operation = operation;
 | 
				
			||||||
 | 
					      item.value = operations[operation];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      yield item;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const oasTagDefined = targetVal => {
 | 
				
			||||||
 | 
					  if (!isObject(targetVal)) return;
 | 
				
			||||||
 | 
					  const results = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const globalTags = [];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (Array.isArray(targetVal.tags)) {
 | 
				
			||||||
 | 
					    for (const tag of targetVal.tags) {
 | 
				
			||||||
 | 
					      if (isObject(tag) && typeof tag.name === 'string') {
 | 
				
			||||||
 | 
					        globalTags.push(tag.name);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { paths } = targetVal;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const { path, operation, value } of getAllOperations(paths)) {
 | 
				
			||||||
 | 
					    if (!isObject(value)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const { tags } = value;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if (!Array.isArray(tags)) {
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const [i, tag] of tags.entries()) {
 | 
				
			||||||
 | 
					      if (!globalTags.includes(tag)) {
 | 
				
			||||||
 | 
					        results.push({
 | 
				
			||||||
 | 
					          message: 'Operation tags must be defined in global tags.',
 | 
				
			||||||
 | 
					          path: ['paths', path, operation, 'tags', i],
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return results;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default oasTagDefined;
 | 
				
			||||||
							
								
								
									
										50
									
								
								.stoplight/custom-functions/oasUnusedComponent.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								.stoplight/custom-functions/oasUnusedComponent.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					import { unreferencedReusableObject } from '@stoplight/spectral-functions';
 | 
				
			||||||
 | 
					import { createRulesetFunction } from '@stoplight/spectral-core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function isObject(value) {
 | 
				
			||||||
 | 
					  return value !== null && typeof value === 'object';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default createRulesetFunction(
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    input: {
 | 
				
			||||||
 | 
					      type: 'object',
 | 
				
			||||||
 | 
					      properties: {
 | 
				
			||||||
 | 
					        components: {
 | 
				
			||||||
 | 
					          type: 'object',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      required: ['components'],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    options: null,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  function oasUnusedComponent(targetVal, opts, context) {
 | 
				
			||||||
 | 
					    const results = [];
 | 
				
			||||||
 | 
					    const componentTypes = [
 | 
				
			||||||
 | 
					      'schemas',
 | 
				
			||||||
 | 
					      'responses',
 | 
				
			||||||
 | 
					      'parameters',
 | 
				
			||||||
 | 
					      'examples',
 | 
				
			||||||
 | 
					      'requestBodies',
 | 
				
			||||||
 | 
					      'headers',
 | 
				
			||||||
 | 
					      'links',
 | 
				
			||||||
 | 
					      'callbacks',
 | 
				
			||||||
 | 
					    ];
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    for (const type of componentTypes) {
 | 
				
			||||||
 | 
					      const value = targetVal.components[type];
 | 
				
			||||||
 | 
					      if (!isObject(value)) continue;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const resultsForType = unreferencedReusableObject(
 | 
				
			||||||
 | 
					        value,
 | 
				
			||||||
 | 
					        { reusableObjectsLocation: `#/components/${type}` },
 | 
				
			||||||
 | 
					        context,
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      if (resultsForType !== void 0 && Array.isArray(resultsForType)) {
 | 
				
			||||||
 | 
					        results.push(...resultsForType);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return results;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
							
								
								
									
										51
									
								
								.stoplight/custom-functions/refSiblings.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								.stoplight/custom-functions/refSiblings.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					function isObject(value) {
 | 
				
			||||||
 | 
					  return value !== null && typeof value === 'object';
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getParentValue(document, path) {
 | 
				
			||||||
 | 
					  if (path.length === 0) {
 | 
				
			||||||
 | 
					    return null;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let piece = document;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (let i = 0; i < path.length - 1; i += 1) {
 | 
				
			||||||
 | 
					    if (!isObject(piece)) {
 | 
				
			||||||
 | 
					      return null;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    piece = piece[path[i]];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return piece;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const refSiblings = (targetVal, opts, { document, path }) => {
 | 
				
			||||||
 | 
					  const value = getParentValue(document.data, path);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!isObject(value)) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const keys = Object.keys(value);
 | 
				
			||||||
 | 
					  if (keys.length === 1) {
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const results = [];
 | 
				
			||||||
 | 
					  const actualObjPath = path.slice(0, -1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  for (const key of keys) {
 | 
				
			||||||
 | 
					    if (key === '$ref') {
 | 
				
			||||||
 | 
					      continue;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					    results.push({
 | 
				
			||||||
 | 
					      message: '$ref must not be placed next to any other properties',
 | 
				
			||||||
 | 
					      path: [...actualObjPath, key],
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return results;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default refSiblings;
 | 
				
			||||||
							
								
								
									
										92
									
								
								.stoplight/custom-functions/typedEnum.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								.stoplight/custom-functions/typedEnum.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,92 @@
 | 
				
			|||||||
 | 
					import { oas2, oas3_0 } from '@stoplight/spectral-formats';
 | 
				
			||||||
 | 
					import { printValue } from '@stoplight/spectral-runtime';
 | 
				
			||||||
 | 
					import { createRulesetFunction } from '@stoplight/spectral-core';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getDataType(input, checkForInteger) {
 | 
				
			||||||
 | 
					  const type = typeof input;
 | 
				
			||||||
 | 
					  switch (type) {
 | 
				
			||||||
 | 
					    case 'string':
 | 
				
			||||||
 | 
					    case 'boolean':
 | 
				
			||||||
 | 
					      return type;
 | 
				
			||||||
 | 
					    case 'number':
 | 
				
			||||||
 | 
					      if (checkForInteger && Number.isInteger(input)) {
 | 
				
			||||||
 | 
					        return 'integer';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return 'number';
 | 
				
			||||||
 | 
					    case 'object':
 | 
				
			||||||
 | 
					      if (input === null) {
 | 
				
			||||||
 | 
					        return 'null';
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return Array.isArray(input) ? 'array' : 'object';
 | 
				
			||||||
 | 
					    default:
 | 
				
			||||||
 | 
					      throw TypeError('Unknown input type');
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function getTypes(input, formats) {
 | 
				
			||||||
 | 
					  const { type } = input;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (
 | 
				
			||||||
 | 
					    (input.nullable === true && formats?.has(oas3_0) === true) ||
 | 
				
			||||||
 | 
					    (input['x-nullable'] === true && formats?.has(oas2) === true)
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    return Array.isArray(type) ? [...type, 'null'] : [type, 'null'];
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return type;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export const typedEnum = createRulesetFunction(
 | 
				
			||||||
 | 
					  {
 | 
				
			||||||
 | 
					    input: {
 | 
				
			||||||
 | 
					      type: 'object',
 | 
				
			||||||
 | 
					      properties: {
 | 
				
			||||||
 | 
					        enum: {
 | 
				
			||||||
 | 
					          type: 'array',
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					        type: {
 | 
				
			||||||
 | 
					          oneOf: [
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              type: 'array',
 | 
				
			||||||
 | 
					              items: {
 | 
				
			||||||
 | 
					                type: 'string',
 | 
				
			||||||
 | 
					              },
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					            {
 | 
				
			||||||
 | 
					              type: 'string',
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					          ],
 | 
				
			||||||
 | 
					        },
 | 
				
			||||||
 | 
					      },
 | 
				
			||||||
 | 
					      required: ['enum', 'type'],
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    options: null,
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					  function (input, opts, context) {
 | 
				
			||||||
 | 
					    const { enum: enumValues } = input;
 | 
				
			||||||
 | 
					    const type = getTypes(input, context.document.formats);
 | 
				
			||||||
 | 
					    const checkForInteger = type === 'integer' || (Array.isArray(type) && type.includes('integer'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    let results;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    enumValues.forEach((value, i) => {
 | 
				
			||||||
 | 
					      const valueType = getDataType(value, checkForInteger);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (valueType === type || (Array.isArray(type) && type.includes(valueType))) {
 | 
				
			||||||
 | 
					        return;
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      results ??= [];
 | 
				
			||||||
 | 
					      results.push({
 | 
				
			||||||
 | 
					        message: `Enum value ${printValue(enumValues[i])} must be "${String(type)}".`,
 | 
				
			||||||
 | 
					        path: [...context.path, 'enum', i],
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return results;
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default typedEnum;
 | 
				
			||||||
							
								
								
									
										2868
									
								
								.stoplight/styleguide.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2868
									
								
								.stoplight/styleguide.json
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							@@ -7,6 +7,7 @@ script:
 | 
				
			|||||||
   - yarn coverage
 | 
					   - yarn coverage
 | 
				
			||||||
before_install:
 | 
					before_install:
 | 
				
			||||||
   - cd seasoned_api
 | 
					   - cd seasoned_api
 | 
				
			||||||
 | 
					   - cp conf/development.json.example conf/development.json
 | 
				
			||||||
before_script: 
 | 
					before_script: 
 | 
				
			||||||
   - yarn
 | 
					   - yarn
 | 
				
			||||||
   - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
 | 
					   - curl -L https://codeclimate.com/downloads/test-reporter/test-reporter-latest-linux-amd64 > ./cc-test-reporter
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										439
									
								
								reference/seasoned-api.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										439
									
								
								reference/seasoned-api.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,439 @@
 | 
				
			|||||||
 | 
					openapi: 3.1.0
 | 
				
			||||||
 | 
					x-stoplight:
 | 
				
			||||||
 | 
					  id: lu1x37qqzll6m
 | 
				
			||||||
 | 
					info:
 | 
				
			||||||
 | 
					  title: seasoned api
 | 
				
			||||||
 | 
					  version: '1.0'
 | 
				
			||||||
 | 
					  summary: Season your media library with the shows and movies that you and your friends want.
 | 
				
			||||||
 | 
					  description: |
 | 
				
			||||||
 | 
					    This is the backend api for [seasoned request] that allows for uesrs to request movies and shows by fetching movies from themoviedb api and checks them with your plex library to identify if a movie is already present or not. This api allows to search my query, get themoviedb movie lists like popular and now playing, all while checking if the item is already in your plex library. Your friends can create users to see what movies or shows they have requested and searched for.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    The api also uses torrent_search to search for matching torrents and returns results from any site or service available from torrent_search. As a admin of the site you can query torrent_search and return a magnet link that can be added to a autoadd folder of your favorite torrent client.
 | 
				
			||||||
 | 
					servers:
 | 
				
			||||||
 | 
					  - url: 'https://request.movie/api'
 | 
				
			||||||
 | 
					    description: poduction
 | 
				
			||||||
 | 
					  - url: 'https://localhost:31459'
 | 
				
			||||||
 | 
					    description: localhost
 | 
				
			||||||
 | 
					paths:
 | 
				
			||||||
 | 
					  /v2/movie/now_playing:
 | 
				
			||||||
 | 
					    get:
 | 
				
			||||||
 | 
					      summary: Your GET endpoint
 | 
				
			||||||
 | 
					      tags: []
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        '200':
 | 
				
			||||||
 | 
					          description: OK
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                type: object
 | 
				
			||||||
 | 
					                properties:
 | 
				
			||||||
 | 
					                  results:
 | 
				
			||||||
 | 
					                    type: array
 | 
				
			||||||
 | 
					                    items:
 | 
				
			||||||
 | 
					                      type: object
 | 
				
			||||||
 | 
					                      properties:
 | 
				
			||||||
 | 
					                        id:
 | 
				
			||||||
 | 
					                          type: integer
 | 
				
			||||||
 | 
					                        title:
 | 
				
			||||||
 | 
					                          type: string
 | 
				
			||||||
 | 
					                        year:
 | 
				
			||||||
 | 
					                          type: integer
 | 
				
			||||||
 | 
					                        overview:
 | 
				
			||||||
 | 
					                          type: string
 | 
				
			||||||
 | 
					                        poster:
 | 
				
			||||||
 | 
					                          type: string
 | 
				
			||||||
 | 
					                        backdrop:
 | 
				
			||||||
 | 
					                          type: string
 | 
				
			||||||
 | 
					                        release_date:
 | 
				
			||||||
 | 
					                          type: string
 | 
				
			||||||
 | 
					                        rating:
 | 
				
			||||||
 | 
					                          type: number
 | 
				
			||||||
 | 
					                        type:
 | 
				
			||||||
 | 
					                          type: string
 | 
				
			||||||
 | 
					                  page:
 | 
				
			||||||
 | 
					                    type: integer
 | 
				
			||||||
 | 
					                  total_results:
 | 
				
			||||||
 | 
					                    type: integer
 | 
				
			||||||
 | 
					                  total_pages:
 | 
				
			||||||
 | 
					                    type: integer
 | 
				
			||||||
 | 
					                x-examples:
 | 
				
			||||||
 | 
					                  example-1:
 | 
				
			||||||
 | 
					                    results:
 | 
				
			||||||
 | 
					                      - id: 616037
 | 
				
			||||||
 | 
					                        title: 'Thor: Love and Thunder'
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'After his retirement is interrupted by Gorr the God Butcher, a galactic killer who seeks the extinction of the gods, Thor enlists the help of King Valkyrie, Korg, and ex-girlfriend Jane Foster, who now inexplicably wields Mjolnir as the Mighty Thor. Together they embark upon a harrowing cosmic adventure to uncover the mystery of the God Butcher’s vengeance and stop him before it’s too late.'
 | 
				
			||||||
 | 
					                        poster: /pIkRyD18kl4FhoCNQuWxWu5cBLM.jpg
 | 
				
			||||||
 | 
					                        backdrop: /p1F51Lvj3sMopG948F5HsBbl43C.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-07-06T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 6.8
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 507086
 | 
				
			||||||
 | 
					                        title: Jurassic World Dominion
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'Four years after Isla Nublar was destroyed, dinosaurs now live—and hunt—alongside humans all over the world. This fragile balance will reshape the future and determine, once and for all, whether human beings are to remain the apex predators on a planet they now share with history’s most fearsome creatures.'
 | 
				
			||||||
 | 
					                        poster: /kAVRgw7GgK1CfYEJq8ME6EvRIgU.jpg
 | 
				
			||||||
 | 
					                        backdrop: /9eAn20y26wtB3aet7w9lHjuSgZ3.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-06-01T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 7.1
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 438148
 | 
				
			||||||
 | 
					                        title: 'Minions: The Rise of Gru'
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'A fanboy of a supervillain supergroup known as the Vicious 6, Gru hatches a plan to become evil enough to join them, with the backup of his followers, the Minions.'
 | 
				
			||||||
 | 
					                        poster: /wKiOkZTN9lUUUNZLmtnwubZYONg.jpg
 | 
				
			||||||
 | 
					                        backdrop: /nmGWzTLMXy9x7mKd8NKPLmHtWGa.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-06-29T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 7.8
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 585511
 | 
				
			||||||
 | 
					                        title: Luck
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'Suddenly finding herself in the never-before-seen Land of Luck, the unluckiest person in the world must unite with the magical creatures there to turn her luck around.'
 | 
				
			||||||
 | 
					                        poster: /1HOYvwGFioUFL58UVvDRG6beEDm.jpg
 | 
				
			||||||
 | 
					                        backdrop: /3VQj6m0I6gkuRaljmhNZl0XR3by.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-08-05T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 8.1
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 756999
 | 
				
			||||||
 | 
					                        title: The Black Phone
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'Finney Blake, a shy but clever 13-year-old boy, is abducted by a sadistic killer and trapped in a soundproof basement where screaming is of little use. When a disconnected phone on the wall begins to ring, Finney discovers that he can hear the voices of the killer’s previous victims. And they are dead set on making sure that what happened to them doesn’t happen to Finney.'
 | 
				
			||||||
 | 
					                        poster: /lr11mCT85T1JanlgjMuhs9nMht4.jpg
 | 
				
			||||||
 | 
					                        backdrop: /jqVyOIz8jxH0NUlc0QUHmV0uOcn.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-06-22T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 8
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 610150
 | 
				
			||||||
 | 
					                        title: 'Dragon Ball Super: Super Hero'
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'The Red Ribbon Army, an evil organization that was once destroyed by Goku in the past, has been reformed by a group of people who have created new and mightier Androids, Gamma 1 and Gamma 2, and seek vengeance against Goku and his family.'
 | 
				
			||||||
 | 
					                        poster: /rugyJdeoJm7cSJL1q4jBpTNbxyU.jpg
 | 
				
			||||||
 | 
					                        backdrop: /uR0FopHrAjDlG5q6PZB07a1JOva.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-06-11T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 7.5
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 760104
 | 
				
			||||||
 | 
					                        title: X
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'In 1979, a group of young filmmakers set out to make an adult film in rural Texas, but when their reclusive, elderly hosts catch them in the act, the cast find themselves fighting for their lives.'
 | 
				
			||||||
 | 
					                        poster: /woTQx9Q4b8aO13jR9dsj8C9JESy.jpg
 | 
				
			||||||
 | 
					                        backdrop: /2oXQpm0wfOkIL0jBJABbL5AfMs6.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-03-17T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 6.7
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 718789
 | 
				
			||||||
 | 
					                        title: Lightyear
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: Legendary Space Ranger Buzz Lightyear embarks on an intergalactic adventure alongside a group of ambitious recruits and his robot companion Sox.
 | 
				
			||||||
 | 
					                        poster: /ox4goZd956BxqJH6iLwhWPL9ct4.jpg
 | 
				
			||||||
 | 
					                        backdrop: /nW5fUbldp1DYf2uQ3zJTUdachOu.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-06-15T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 7.3
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 725201
 | 
				
			||||||
 | 
					                        title: The Gray Man
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'When a shadowy CIA agent uncovers damning agency secrets, he''s hunted across the globe by a sociopathic rogue operative who''s put a bounty on his head.'
 | 
				
			||||||
 | 
					                        poster: /8cXbitsS6dWQ5gfMTZdorpAAzEH.jpg
 | 
				
			||||||
 | 
					                        backdrop: /27Mj3rFYP3xqFy7lnz17vEd8Ms.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-07-13T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 7
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 758724
 | 
				
			||||||
 | 
					                        title: The Cellar
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'When Keira Woods'' daughter mysteriously vanishes in the cellar of their new house in the country, she soon discovers there is an ancient and powerful entity controlling their home that she will have to face or risk losing her family''s souls forever.'
 | 
				
			||||||
 | 
					                        poster: /rtfGeS5WMXA6PtikIYUmYTSbVdg.jpg
 | 
				
			||||||
 | 
					                        backdrop: /qViFGWCHaSmW4gP00RGh3xjMjsP.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-03-25T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 6.6
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 961484
 | 
				
			||||||
 | 
					                        title: Last Seen Alive
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'After Will Spann''s wife suddenly vanishes at a gas station, his desperate search to find her leads him down a dark path that forces him to run from authorities and take the law into his own hands.'
 | 
				
			||||||
 | 
					                        poster: /qvqyDj34Uivokf4qIvK4bH0m0qF.jpg
 | 
				
			||||||
 | 
					                        backdrop: /ftGzl2GCyko61Qp161bQElN2Uzd.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-05-19T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 6.6
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 675353
 | 
				
			||||||
 | 
					                        title: Sonic the Hedgehog 2
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'After settling in Green Hills, Sonic is eager to prove he has what it takes to be a true hero. His test comes when Dr. Robotnik returns, this time with a new partner, Knuckles, in search for an emerald that has the power to destroy civilizations. Sonic teams up with his own sidekick, Tails, and together they embark on a globe-trotting journey to find the emerald before it falls into the wrong hands.'
 | 
				
			||||||
 | 
					                        poster: /6DrHO1jr3qVrViUO6s6kFiAGM7.jpg
 | 
				
			||||||
 | 
					                        backdrop: /8wwXPG22aNMpPGuXnfm3galoxbI.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-03-30T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 7.7
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 924482
 | 
				
			||||||
 | 
					                        title: The Ledge
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'A rock climbing adventure between two friends turns into a terrifying nightmare. After Kelly captures the murder of her best friend on camera, she becomes the next target of a tight-knit group of friends who will stop at nothing to destroy the evidence and anyone in their way. Desperate for her safety, she begins a treacherous climb up a mountain cliff and her survival instincts are put to the test when she becomes trapped with the killers just 20 feet away.'
 | 
				
			||||||
 | 
					                        poster: /dHKfsdNcEPw7YIWFPIhqiuWrSAb.jpg
 | 
				
			||||||
 | 
					                        backdrop: /jazlkwXfw4KdF6fVTRsolOvRCmu.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-02-18T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 6.3
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 698948
 | 
				
			||||||
 | 
					                        title: Thirteen Lives
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'Based on the true nail-biting mission that captivated the world. Twelve boys and the coach of a Thai soccer team explore the Tham Luang cave when an unexpected rainstorm traps them in a chamber inside the mountain. Entombed behind a maze of flooded cave tunnels, they face impossible odds. A team of world-class divers navigate through miles of dangerous cave networks to discover that finding the boys is only the beginning.'
 | 
				
			||||||
 | 
					                        poster: /yi5KcJqFxy0D6yP8nCfcF8gJGg5.jpg
 | 
				
			||||||
 | 
					                        backdrop: /tHR34A5n0my4maACNdLpWGd6QYq.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-07-18T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 8
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 614934
 | 
				
			||||||
 | 
					                        title: Elvis
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'The life story of Elvis Presley as seen through the complicated relationship with his enigmatic manager, Colonel Tom Parker.'
 | 
				
			||||||
 | 
					                        poster: /qBOKWqAFbveZ4ryjJJwbie6tXkQ.jpg
 | 
				
			||||||
 | 
					                        backdrop: /rLo9T9jEg67UZPq3midjLnTUYYi.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-06-22T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 7.9
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 639933
 | 
				
			||||||
 | 
					                        title: The Northman
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'Prince Amleth is on the verge of becoming a man when his father is brutally murdered by his uncle, who kidnaps the boy''s mother. Two decades later, Amleth is now a Viking who''s on a mission to save his mother, kill his uncle and avenge his father.'
 | 
				
			||||||
 | 
					                        poster: /8p9zXB7M78nZpm215zHfqpknMeM.jpg
 | 
				
			||||||
 | 
					                        backdrop: /k2G4WqGiT60K9yJnPh4K6VLnl3A.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-04-07T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 7.2
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 894169
 | 
				
			||||||
 | 
					                        title: Vendetta
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'When his daughter is murdered, William Duncan takes the law into his own hands, setting out on a quest for retribution. After killing the street thug responsible for her death, he finds himself in the middle of a war with the thug''s brother, father, and their gang, who are equally hell-bent on getting even. What ensues is a tense back-and-forth game of vengeance. By the end, William comes to find that the quest for revenge never has a winner.'
 | 
				
			||||||
 | 
					                        poster: /7InGE2Sux0o9WGbbn0bl7nZzqEc.jpg
 | 
				
			||||||
 | 
					                        backdrop: /33qGtN2GpGEb94pn25PDPeWQZLk.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-05-17T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 6.5
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 718930
 | 
				
			||||||
 | 
					                        title: Bullet Train
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'Unlucky assassin Ladybug is determined to do his job peacefully after one too many gigs gone off the rails. Fate, however, may have other plans, as Ladybug''s latest mission puts him on a collision course with lethal adversaries from around the globe—all with connected, yet conflicting, objectives—on the world''s fastest train.'
 | 
				
			||||||
 | 
					                        poster: /rTgfp0ZuikSUK8HK8Jgn3PUqteH.jpg
 | 
				
			||||||
 | 
					                        backdrop: /C8FpZfTPEZDjngPlatiFsaDB4A.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-07-03T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 7.4
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 697799
 | 
				
			||||||
 | 
					                        title: WarHunt
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: '1945. A U.S. military cargo plane loses control and violently crashes behind enemy lines in the middle of the German black forest. Major Johnson sends a squad of his bravest soldiers on a rescue mission to retrieve the top-secret material the plane was carrying, led by Sergeants Brewer and Walsh. They soon discover hanged Nazi soldiers and other dead bodies bearing ancient, magical symbols. Suddenly their compasses fail, their perceptions twist and straying from the group leads to profound horrors as they are attacked by a powerful, supernatural force.'
 | 
				
			||||||
 | 
					                        poster: /9HFFwZOTBB7IPFmn9E0MXdWave3.jpg
 | 
				
			||||||
 | 
					                        backdrop: /mTupUmnuwwAyA0CNqpwaZn5mqjk.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-01-21T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 5.1
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                      - id: 818397
 | 
				
			||||||
 | 
					                        title: Memory
 | 
				
			||||||
 | 
					                        year: 2022
 | 
				
			||||||
 | 
					                        overview: 'Alex, an assassin-for-hire, finds that he''s become a target after he refuses to complete a job for a dangerous criminal organization. With the crime syndicate and FBI in hot pursuit, Alex has the skills to stay ahead, except for one thing: he is struggling with severe memory loss, affecting his every move. Alex must question his every action and whom he can ultimately trust.'
 | 
				
			||||||
 | 
					                        poster: /4Q1n3TwieoULnuaztu9aFjqHDTI.jpg
 | 
				
			||||||
 | 
					                        backdrop: /vjnLXptqdxnpNJer5fWgj2OIGhL.jpg
 | 
				
			||||||
 | 
					                        release_date: '2022-04-28T00:00:00.000Z'
 | 
				
			||||||
 | 
					                        rating: 7.3
 | 
				
			||||||
 | 
					                        type: movie
 | 
				
			||||||
 | 
					                    page: 1
 | 
				
			||||||
 | 
					                    total_results: 1241
 | 
				
			||||||
 | 
					                    total_pages: 63
 | 
				
			||||||
 | 
					      operationId: get-v2-movie-now_playing
 | 
				
			||||||
 | 
					      description: ''
 | 
				
			||||||
 | 
					      security:
 | 
				
			||||||
 | 
					        - authorization: []
 | 
				
			||||||
 | 
					  '/users/{userId}':
 | 
				
			||||||
 | 
					    parameters:
 | 
				
			||||||
 | 
					      - schema:
 | 
				
			||||||
 | 
					          type: integer
 | 
				
			||||||
 | 
					        name: userId
 | 
				
			||||||
 | 
					        in: path
 | 
				
			||||||
 | 
					        required: true
 | 
				
			||||||
 | 
					        description: Id of an existing user.
 | 
				
			||||||
 | 
					    get:
 | 
				
			||||||
 | 
					      summary: Get User Info by User ID
 | 
				
			||||||
 | 
					      tags: []
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        '200':
 | 
				
			||||||
 | 
					          description: User Found
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: '#/components/schemas/User'
 | 
				
			||||||
 | 
					              examples:
 | 
				
			||||||
 | 
					                Get User Alice Smith:
 | 
				
			||||||
 | 
					                  value:
 | 
				
			||||||
 | 
					                    id: 142
 | 
				
			||||||
 | 
					                    firstName: Alice
 | 
				
			||||||
 | 
					                    lastName: Smith
 | 
				
			||||||
 | 
					                    email: alice.smith@gmail.com
 | 
				
			||||||
 | 
					                    dateOfBirth: '1997-10-31'
 | 
				
			||||||
 | 
					                    emailVerified: true
 | 
				
			||||||
 | 
					                    signUpDate: '2019-08-24'
 | 
				
			||||||
 | 
					        '404':
 | 
				
			||||||
 | 
					          description: User Not Found
 | 
				
			||||||
 | 
					      operationId: get-users-userId
 | 
				
			||||||
 | 
					      description: Retrieve the information of the user with the matching user ID.
 | 
				
			||||||
 | 
					    patch:
 | 
				
			||||||
 | 
					      summary: Update User Information
 | 
				
			||||||
 | 
					      operationId: patch-users-userId
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        '200':
 | 
				
			||||||
 | 
					          description: User Updated
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: '#/components/schemas/User'
 | 
				
			||||||
 | 
					              examples:
 | 
				
			||||||
 | 
					                Updated User Rebecca Baker:
 | 
				
			||||||
 | 
					                  value:
 | 
				
			||||||
 | 
					                    id: 13
 | 
				
			||||||
 | 
					                    firstName: Rebecca
 | 
				
			||||||
 | 
					                    lastName: Baker
 | 
				
			||||||
 | 
					                    email: rebecca@gmail.com
 | 
				
			||||||
 | 
					                    dateOfBirth: '1985-10-02'
 | 
				
			||||||
 | 
					                    emailVerified: false
 | 
				
			||||||
 | 
					                    createDate: '2019-08-24'
 | 
				
			||||||
 | 
					        '404':
 | 
				
			||||||
 | 
					          description: User Not Found
 | 
				
			||||||
 | 
					        '409':
 | 
				
			||||||
 | 
					          description: Email Already Taken
 | 
				
			||||||
 | 
					      description: Update the information of an existing user.
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
 | 
					              properties:
 | 
				
			||||||
 | 
					                firstName:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                lastName:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                email:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  description: 'If a new email is given, the user''s email verified property will be set to false.'
 | 
				
			||||||
 | 
					                dateOfBirth:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					            examples:
 | 
				
			||||||
 | 
					              Update First Name:
 | 
				
			||||||
 | 
					                value:
 | 
				
			||||||
 | 
					                  firstName: Rebecca
 | 
				
			||||||
 | 
					              Update Email:
 | 
				
			||||||
 | 
					                value:
 | 
				
			||||||
 | 
					                  email: rebecca@gmail.com
 | 
				
			||||||
 | 
					              Update Last Name & Date of Birth:
 | 
				
			||||||
 | 
					                value:
 | 
				
			||||||
 | 
					                  lastName: Baker
 | 
				
			||||||
 | 
					                  dateOfBirth: '1985-10-02'
 | 
				
			||||||
 | 
					        description: Patch user properties to update.
 | 
				
			||||||
 | 
					  /user:
 | 
				
			||||||
 | 
					    post:
 | 
				
			||||||
 | 
					      summary: Create New User
 | 
				
			||||||
 | 
					      operationId: post-user
 | 
				
			||||||
 | 
					      responses:
 | 
				
			||||||
 | 
					        '200':
 | 
				
			||||||
 | 
					          description: User Created
 | 
				
			||||||
 | 
					          content:
 | 
				
			||||||
 | 
					            application/json:
 | 
				
			||||||
 | 
					              schema:
 | 
				
			||||||
 | 
					                $ref: '#/components/schemas/User'
 | 
				
			||||||
 | 
					              examples:
 | 
				
			||||||
 | 
					                New User Bob Fellow:
 | 
				
			||||||
 | 
					                  value:
 | 
				
			||||||
 | 
					                    id: 12
 | 
				
			||||||
 | 
					                    firstName: Bob
 | 
				
			||||||
 | 
					                    lastName: Fellow
 | 
				
			||||||
 | 
					                    email: bob.fellow@gmail.com
 | 
				
			||||||
 | 
					                    dateOfBirth: '1996-08-24'
 | 
				
			||||||
 | 
					                    emailVerified: false
 | 
				
			||||||
 | 
					                    createDate: '2020-11-18'
 | 
				
			||||||
 | 
					        '400':
 | 
				
			||||||
 | 
					          description: Missing Required Information
 | 
				
			||||||
 | 
					        '409':
 | 
				
			||||||
 | 
					          description: Email Already Taken
 | 
				
			||||||
 | 
					      requestBody:
 | 
				
			||||||
 | 
					        content:
 | 
				
			||||||
 | 
					          application/json:
 | 
				
			||||||
 | 
					            schema:
 | 
				
			||||||
 | 
					              type: object
 | 
				
			||||||
 | 
					              properties:
 | 
				
			||||||
 | 
					                firstName:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                lastName:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                email:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                dateOfBirth:
 | 
				
			||||||
 | 
					                  type: string
 | 
				
			||||||
 | 
					                  format: date
 | 
				
			||||||
 | 
					              required:
 | 
				
			||||||
 | 
					                - firstName
 | 
				
			||||||
 | 
					                - lastName
 | 
				
			||||||
 | 
					                - email
 | 
				
			||||||
 | 
					                - dateOfBirth
 | 
				
			||||||
 | 
					            examples:
 | 
				
			||||||
 | 
					              Create User Bob Fellow:
 | 
				
			||||||
 | 
					                value:
 | 
				
			||||||
 | 
					                  firstName: Bob
 | 
				
			||||||
 | 
					                  lastName: Fellow
 | 
				
			||||||
 | 
					                  email: bob.fellow@gmail.com
 | 
				
			||||||
 | 
					                  dateOfBirth: '1996-08-24'
 | 
				
			||||||
 | 
					        description: Post the necessary fields for the API to create a new user.
 | 
				
			||||||
 | 
					      description: Create a new user.
 | 
				
			||||||
 | 
					components:
 | 
				
			||||||
 | 
					  schemas:
 | 
				
			||||||
 | 
					    User:
 | 
				
			||||||
 | 
					      title: User
 | 
				
			||||||
 | 
					      type: object
 | 
				
			||||||
 | 
					      description: ''
 | 
				
			||||||
 | 
					      examples:
 | 
				
			||||||
 | 
					        - id: 142
 | 
				
			||||||
 | 
					          firstName: Alice
 | 
				
			||||||
 | 
					          lastName: Smith
 | 
				
			||||||
 | 
					          email: alice.smith@gmail.com
 | 
				
			||||||
 | 
					          dateOfBirth: '1997-10-31'
 | 
				
			||||||
 | 
					          emailVerified: true
 | 
				
			||||||
 | 
					          signUpDate: '2019-08-24'
 | 
				
			||||||
 | 
					      properties:
 | 
				
			||||||
 | 
					        id:
 | 
				
			||||||
 | 
					          type: integer
 | 
				
			||||||
 | 
					          description: Unique identifier for the given user.
 | 
				
			||||||
 | 
					        firstName:
 | 
				
			||||||
 | 
					          type: string
 | 
				
			||||||
 | 
					        lastName:
 | 
				
			||||||
 | 
					          type: string
 | 
				
			||||||
 | 
					        email:
 | 
				
			||||||
 | 
					          type: string
 | 
				
			||||||
 | 
					          format: email
 | 
				
			||||||
 | 
					        dateOfBirth:
 | 
				
			||||||
 | 
					          type: string
 | 
				
			||||||
 | 
					          format: date
 | 
				
			||||||
 | 
					          example: '1997-10-31'
 | 
				
			||||||
 | 
					        emailVerified:
 | 
				
			||||||
 | 
					          type: boolean
 | 
				
			||||||
 | 
					          description: Set to true if the user's email has been verified.
 | 
				
			||||||
 | 
					        createDate:
 | 
				
			||||||
 | 
					          type: string
 | 
				
			||||||
 | 
					          format: date
 | 
				
			||||||
 | 
					          description: The date that the user was created.
 | 
				
			||||||
 | 
					      required:
 | 
				
			||||||
 | 
					        - id
 | 
				
			||||||
 | 
					        - firstName
 | 
				
			||||||
 | 
					        - lastName
 | 
				
			||||||
 | 
					        - email
 | 
				
			||||||
 | 
					        - emailVerified
 | 
				
			||||||
 | 
					  securitySchemes:
 | 
				
			||||||
 | 
					    authorization:
 | 
				
			||||||
 | 
					      name: Authorization token
 | 
				
			||||||
 | 
					      type: apiKey
 | 
				
			||||||
 | 
					      in: header
 | 
				
			||||||
 | 
					      description: |-
 | 
				
			||||||
 | 
					        An authorization token is a token that you provide when making API calls. Include the token in a header parameter called Authorization.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Example: `Authorization: eyJhbGciOiJIUzI1NiIsInR5cCI`
 | 
				
			||||||
 | 
					security:
 | 
				
			||||||
 | 
					  - authorization: []
 | 
				
			||||||
@@ -1,25 +1,26 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
	"database": {
 | 
					  "database": {
 | 
				
			||||||
		"host": "../shows.db"
 | 
					    "host": "../shows.db"
 | 
				
			||||||
	},
 | 
					  },
 | 
				
			||||||
	"webserver": {
 | 
					  "webserver": {
 | 
				
			||||||
		"port": 31459
 | 
					    "port": 31459,
 | 
				
			||||||
	},
 | 
					    "origins": []
 | 
				
			||||||
	"tmdb": {
 | 
					  },
 | 
				
			||||||
		"apiKey": ""
 | 
					  "tmdb": {
 | 
				
			||||||
	},
 | 
					    "apiKey": ""
 | 
				
			||||||
	"plex": {
 | 
					  },
 | 
				
			||||||
		"ip": ""
 | 
					  "plex": {
 | 
				
			||||||
	},
 | 
					    "ip": ""
 | 
				
			||||||
 | 
					  },
 | 
				
			||||||
  "tautulli": {
 | 
					  "tautulli": {
 | 
				
			||||||
    "apiKey": "",
 | 
					    "apiKey": "",
 | 
				
			||||||
    "ip": "",
 | 
					    "ip": "",
 | 
				
			||||||
    "port": ""
 | 
					    "port": ""
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
	"raven": {
 | 
					  "raven": {
 | 
				
			||||||
		"DSN": ""
 | 
					    "DSN": ""
 | 
				
			||||||
	},
 | 
					  },
 | 
				
			||||||
	"authentication": {
 | 
					  "authentication": {
 | 
				
			||||||
    "secret": "secret"
 | 
					    "secret": "secret"
 | 
				
			||||||
 	}
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,31 +7,33 @@
 | 
				
			|||||||
  },
 | 
					  },
 | 
				
			||||||
  "main": "webserver/server.js",
 | 
					  "main": "webserver/server.js",
 | 
				
			||||||
  "scripts": {
 | 
					  "scripts": {
 | 
				
			||||||
    "start": "cross-env SEASONED_CONFIG=conf/development.json PROD=true NODE_PATH=. babel-node src/webserver/server.js",
 | 
					    "start": "cross-env SEASONED_CONFIG=conf/development.json NODE_ENV=production NODE_PATH=. babel-node src/webserver/server.js",
 | 
				
			||||||
    "test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --require @babel/register --recursive test/unit test/system",
 | 
					    "test": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. mocha --require @babel/register --recursive test/unit test/system",
 | 
				
			||||||
    "coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --require @babel/register --recursive test && nyc report --reporter=text-lcov | coveralls",
 | 
					    "coverage": "cross-env SEASONED_CONFIG=conf/test.json NODE_PATH=. nyc mocha --require @babel/register --recursive test && nyc report --reporter=text-lcov | coveralls",
 | 
				
			||||||
    "lint": "./node_modules/.bin/eslint src/",
 | 
					    "lint": "./node_modules/.bin/eslint src/",
 | 
				
			||||||
    "update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node src/plex/updateRequestsInPlex.js",
 | 
					    "update": "cross-env SEASONED_CONFIG=conf/development.json NODE_PATH=. node scripts/updateRequestsInPlex.js",
 | 
				
			||||||
    "docs": "yarn apiDocs; yarn classDocs",
 | 
					    "docs": "yarn apiDocs; yarn classDocs",
 | 
				
			||||||
    "apiDocs": "",
 | 
					    "apiDocs": "",
 | 
				
			||||||
    "classDocs": "./script/generate-class-docs.sh"
 | 
					    "classDocs": "./script/generate-class-docs.sh"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "axios": "^0.18.0",
 | 
					    "axios": "^0.18.0",
 | 
				
			||||||
    "bcrypt": "^3.0.6",
 | 
					    "bcrypt": "^5.0.1",
 | 
				
			||||||
    "body-parser": "~1.18.2",
 | 
					    "body-parser": "~1.18.2",
 | 
				
			||||||
 | 
					    "cookie-parser": "^1.4.6",
 | 
				
			||||||
    "cross-env": "~5.1.4",
 | 
					    "cross-env": "~5.1.4",
 | 
				
			||||||
    "express": "~4.16.0",
 | 
					    "express": "~4.16.0",
 | 
				
			||||||
    "form-data": "^2.5.1",
 | 
					    "form-data": "^2.5.1",
 | 
				
			||||||
    "jsonwebtoken": "^8.2.0",
 | 
					    "jsonwebtoken": "^8.5.1",
 | 
				
			||||||
    "km-moviedb": "^0.2.12",
 | 
					    "km-moviedb": "^0.2.12",
 | 
				
			||||||
    "node-cache": "^4.1.1",
 | 
					    "node-cache": "^4.1.1",
 | 
				
			||||||
    "node-fetch": "^2.6.0",
 | 
					    "node-fetch": "^2.6.0",
 | 
				
			||||||
    "python-shell": "^0.5.0",
 | 
					    "python-shell": "^0.5.0",
 | 
				
			||||||
    "raven": "^2.4.2",
 | 
					    "raven": "^2.4.2",
 | 
				
			||||||
 | 
					    "redis": "^3.0.2",
 | 
				
			||||||
    "request": "^2.87.0",
 | 
					    "request": "^2.87.0",
 | 
				
			||||||
    "request-promise": "^4.2",
 | 
					    "request-promise": "^4.2",
 | 
				
			||||||
    "sqlite3": "^4.0.0"
 | 
					    "sqlite3": "^5.0.1"
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "devDependencies": {
 | 
					  "devDependencies": {
 | 
				
			||||||
    "@babel/core": "^7.5.5",
 | 
					    "@babel/core": "^7.5.5",
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										44
									
								
								seasoned_api/scripts/updateRequestsInPlex.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										44
									
								
								seasoned_api/scripts/updateRequestsInPlex.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					const Plex = require("src/plex/plex");
 | 
				
			||||||
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
 | 
					const plex = new Plex(configuration.get("plex", "ip"));
 | 
				
			||||||
 | 
					const establishedDatabase = require("src/database/database");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const queries = {
 | 
				
			||||||
 | 
					  getRequestsNotYetInPlex: `SELECT * FROM requests WHERE status = 'requested' OR status = 'downloading'`,
 | 
				
			||||||
 | 
					  saveNewStatus: `UPDATE requests SET status = ? WHERE id IS ? and type IS ?`
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getByStatus = () =>
 | 
				
			||||||
 | 
					  establishedDatabase.all(queries.getRequestsNotYetInPlex);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const checkIfRequestExistInPlex = async request => {
 | 
				
			||||||
 | 
					  request.existsInPlex = await plex.existsInPlex(request);
 | 
				
			||||||
 | 
					  return request;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const commitNewStatus = (status, id, type, title) => {
 | 
				
			||||||
 | 
					  console.log(type, title, "updated to:", status);
 | 
				
			||||||
 | 
					  return establishedDatabase.run(queries.saveNewStatus, [status, id, type]);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const getNewRequestMatchesInPlex = async () => {
 | 
				
			||||||
 | 
					  const requests = await getByStatus();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return Promise.all(requests.map(checkIfRequestExistInPlex))
 | 
				
			||||||
 | 
					    .catch(error =>
 | 
				
			||||||
 | 
					      console.log("error from checking plex for existance:", error)
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .then(matchedRequests =>
 | 
				
			||||||
 | 
					      matchedRequests.filter(request => request.existsInPlex)
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const updateMatchInDb = (match, status) => {
 | 
				
			||||||
 | 
					  return commitNewStatus(status, match.id, match.type, match.title);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					getNewRequestMatchesInPlex()
 | 
				
			||||||
 | 
					  .then(newMatches =>
 | 
				
			||||||
 | 
					    Promise.all(newMatches.map(match => updateMatchInDb(match, "downloaded")))
 | 
				
			||||||
 | 
					  )
 | 
				
			||||||
 | 
					  .then(() => process.exit(0));
 | 
				
			||||||
							
								
								
									
										52
									
								
								seasoned_api/src/cache/redis.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								seasoned_api/src/cache/redis.js
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					const redis = require("redis")
 | 
				
			||||||
 | 
					const client = redis.createClient()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Cache {
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Retrieve an unexpired cache entry by key.
 | 
				
			||||||
 | 
					   * @param {String} key of the cache entry
 | 
				
			||||||
 | 
					   * @returns {Promise}
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  get(key) {
 | 
				
			||||||
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					      client.get(key, (error, reply) => {
 | 
				
			||||||
 | 
					        if (reply == null) {
 | 
				
			||||||
 | 
					          return reject();
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        resolve(JSON.parse(reply));
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  /**
 | 
				
			||||||
 | 
					   * Insert cache entry with key and value.
 | 
				
			||||||
 | 
					   * @param {String} key of the cache entry
 | 
				
			||||||
 | 
					   * @param {String} value of the cache entry
 | 
				
			||||||
 | 
					   * @param {Number} timeToLive the number of seconds before entry expires
 | 
				
			||||||
 | 
					   * @returns {Object}
 | 
				
			||||||
 | 
					   */
 | 
				
			||||||
 | 
					  set(key, value, timeToLive = 10800) {
 | 
				
			||||||
 | 
					    if (value == null || key == null) return null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const json = JSON.stringify(value);
 | 
				
			||||||
 | 
					    client.set(key, json, (error, reply) => {
 | 
				
			||||||
 | 
					      if (reply == "OK") {
 | 
				
			||||||
 | 
					        // successfully set value with key, now set TTL for key
 | 
				
			||||||
 | 
					        client.expire(key, timeToLive, e => {
 | 
				
			||||||
 | 
					          if (e)
 | 
				
			||||||
 | 
					            console.error(
 | 
				
			||||||
 | 
					              "Unexpected error while setting expiration for key:",
 | 
				
			||||||
 | 
					              key,
 | 
				
			||||||
 | 
					              ". Error:",
 | 
				
			||||||
 | 
					              error
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return value;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = Cache;
 | 
				
			||||||
@@ -22,13 +22,13 @@ class Config {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
   get(section, option) {
 | 
					   get(section, option) {
 | 
				
			||||||
      if (this.fields[section] === undefined || this.fields[section][option] === undefined) {
 | 
					      if (this.fields[section] === undefined || this.fields[section][option] === undefined) {
 | 
				
			||||||
         throw new Error(`Filed "${section} => ${option}" does not exist.`);
 | 
					         throw new Error(`Field "${section} => ${option}" does not exist.`);
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      const field = new Field(this.fields[section][option]);
 | 
					      const field = new Field(this.fields[section][option]);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (field.value === '') {
 | 
					      if (field.value === '') {
 | 
				
			||||||
         const envField = process.env[['SEASONED', section.toUpperCase(), option.toUpperCase()].join('_')];
 | 
					         const envField = process.env[[section.toUpperCase(), option.toUpperCase()].join('_')];
 | 
				
			||||||
         if (envField !== undefined && envField.length !== 0) { return envField; }
 | 
					         if (envField !== undefined && envField.length !== 0) { return envField; }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -31,7 +31,7 @@ CREATE TABLE IF NOT EXISTS search_history (
 | 
				
			|||||||
);
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CREATE TABLE IF NOT EXISTS requests(
 | 
					CREATE TABLE IF NOT EXISTS requests(
 | 
				
			||||||
    id TEXT,
 | 
					    id NUMBER,
 | 
				
			||||||
    title TEXT,
 | 
					    title TEXT,
 | 
				
			||||||
    year NUMBER,
 | 
					    year NUMBER,
 | 
				
			||||||
    poster_path TEXT DEFAULT NULL,
 | 
					    poster_path TEXT DEFAULT NULL,
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										35
									
								
								seasoned_api/src/notifications/sms.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								seasoned_api/src/notifications/sms.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,35 @@
 | 
				
			|||||||
 | 
					const request = require("request");
 | 
				
			||||||
 | 
					const configuration = require('src/config/configuration').getInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const sendSMS = (message) => {
 | 
				
			||||||
 | 
					  const apiKey = configuration.get('sms', 'apikey')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (!apiKey) {
 | 
				
			||||||
 | 
					    console.warning("api key for sms not set, cannot send sms.")
 | 
				
			||||||
 | 
					    return null
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const sender = configuration.get('sms', 'sender')
 | 
				
			||||||
 | 
					  const recipient = configuration.get('sms', 'recipient')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return new Promise((resolve, reject) => {
 | 
				
			||||||
 | 
					  request.post(
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      url: `https://gatewayapi.com/rest/mtsms?token=${apiKey}`,
 | 
				
			||||||
 | 
					      json: true,
 | 
				
			||||||
 | 
					      body: {
 | 
				
			||||||
 | 
					        sender,
 | 
				
			||||||
 | 
					        message,
 | 
				
			||||||
 | 
					        recipients: [{ msisdn: `47${recipient}` }]
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    function(err, r, body) {
 | 
				
			||||||
 | 
					      console.log(err ? err : body);
 | 
				
			||||||
 | 
					      console.log("sms provider response:", body)
 | 
				
			||||||
 | 
					      resolve()
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					  })
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = { sendSMS }
 | 
				
			||||||
@@ -1,84 +1,104 @@
 | 
				
			|||||||
const assert = require('assert');
 | 
					const assert = require("assert");
 | 
				
			||||||
const http = require('http');
 | 
					const http = require("http");
 | 
				
			||||||
const { URL } = require('url');
 | 
					const { URL } = require("url");
 | 
				
			||||||
const PythonShell = require('python-shell');
 | 
					const PythonShell = require("python-shell");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const establishedDatabase = require('src/database/database');
 | 
					const establishedDatabase = require("src/database/database");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const RedisCache = require("src/cache/redis");
 | 
				
			||||||
 | 
					const cache = new RedisCache();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getMagnetFromURL(url) {
 | 
					function getMagnetFromURL(url) {
 | 
				
			||||||
   return new Promise((resolve, reject) => {
 | 
					  return new Promise((resolve, reject) => {
 | 
				
			||||||
      const options = new URL(url);
 | 
					    const options = new URL(url);
 | 
				
			||||||
      if (options.protocol.includes('magnet'))
 | 
					    if (options.protocol.includes("magnet")) resolve(url);
 | 
				
			||||||
         resolve(url)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      http.get(options, (res) => {
 | 
					    http.get(options, res => {
 | 
				
			||||||
         if (res.statusCode == 301 || res.statusCode == 302) {
 | 
					      if (res.statusCode == 301 || res.statusCode == 302) {
 | 
				
			||||||
            resolve(res.headers.location)
 | 
					        resolve(res.headers.location);
 | 
				
			||||||
         }
 | 
					      }
 | 
				
			||||||
      });
 | 
					    });
 | 
				
			||||||
   });
 | 
					  });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function find(searchterm, callback) {
 | 
					async function find(searchterm, callback) {
 | 
				
			||||||
   const options = {
 | 
					  const options = {
 | 
				
			||||||
      pythonPath: '../torrent_search/env/bin/python3',
 | 
					    pythonPath: "../torrent_search/env/bin/python3",
 | 
				
			||||||
      scriptPath: '../torrent_search',
 | 
					    scriptPath: "../torrent_search",
 | 
				
			||||||
      args: [searchterm, '-s', 'jackett', '-f', '--print']
 | 
					    args: [searchterm, "-s", "jackett", "--print"]
 | 
				
			||||||
   }
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   PythonShell.run('torrentSearch/search.py', options, callback);
 | 
					  PythonShell.run("torrentSearch/search.py", options, callback);
 | 
				
			||||||
   // PythonShell does not support return
 | 
					  // PythonShell does not support return
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
async function callPythonAddMagnet(url, callback) {
 | 
					async function callPythonAddMagnet(url, callback) {
 | 
				
			||||||
   getMagnetFromURL(url)
 | 
					  getMagnetFromURL(url)
 | 
				
			||||||
   .then((magnet) => {
 | 
					    .then(magnet => {
 | 
				
			||||||
      const options = {
 | 
					      const options = {
 | 
				
			||||||
        pythonPath: '../delugeClient/env/bin/python3',
 | 
					        pythonPath: "../delugeClient/env/bin/python3",
 | 
				
			||||||
        scriptPath: '../delugeClient',
 | 
					        scriptPath: "../delugeClient",
 | 
				
			||||||
        args: ['add', magnet]
 | 
					        args: ["add", magnet]
 | 
				
			||||||
      };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      PythonShell.run('deluge_cli.py', options, callback);
 | 
					      PythonShell.run("deluge_cli.py", options, callback);
 | 
				
			||||||
   })
 | 
					    })
 | 
				
			||||||
   .catch((err) => {
 | 
					    .catch(err => {
 | 
				
			||||||
      console.log(err);
 | 
					      console.log(err);
 | 
				
			||||||
      throw new Error(err);
 | 
					      throw new Error(err);
 | 
				
			||||||
   })
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function SearchPiratebay(query) {
 | 
					async function SearchPiratebay(query) {
 | 
				
			||||||
   return await new Promise((resolve, reject) => find(query, (err, results) => {
 | 
					  if (query && query.includes("+")) {
 | 
				
			||||||
      if (err) {
 | 
					    query = query.replace("+", "%20");
 | 
				
			||||||
         console.log('THERE WAS A FUCKING ERROR!\n', err);
 | 
					  }
 | 
				
			||||||
         reject(Error('There was a error when searching for torrents'));
 | 
					
 | 
				
			||||||
      }
 | 
					  const cacheKey = `pirate/${query}`;
 | 
				
			||||||
      if (results) {
 | 
					
 | 
				
			||||||
         resolve(JSON.parse(results, null, '\t'));
 | 
					  return new Promise((resolve, reject) =>
 | 
				
			||||||
      }
 | 
					    cache
 | 
				
			||||||
   }));
 | 
					      .get(cacheKey)
 | 
				
			||||||
 | 
					      .then(resolve)
 | 
				
			||||||
 | 
					      .catch(() =>
 | 
				
			||||||
 | 
					        find(query, (err, results) => {
 | 
				
			||||||
 | 
					          if (err) {
 | 
				
			||||||
 | 
					            console.log("THERE WAS A FUCKING ERROR!\n", err);
 | 
				
			||||||
 | 
					            reject(Error("There was a error when searching for torrents"));
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          if (results) {
 | 
				
			||||||
 | 
					            const jsonData = JSON.parse(results[1], null, "\t");
 | 
				
			||||||
 | 
					            cache.set(cacheKey, jsonData);
 | 
				
			||||||
 | 
					            resolve(jsonData);
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
async function AddMagnet(magnet, name, tmdb_id) {
 | 
					async function AddMagnet(magnet, name, tmdb_id) {
 | 
				
			||||||
   return await new Promise((resolve, reject) => callPythonAddMagnet(magnet, (err, results) => {
 | 
					  return await new Promise((resolve, reject) =>
 | 
				
			||||||
 | 
					    callPythonAddMagnet(magnet, (err, results) => {
 | 
				
			||||||
      if (err) {
 | 
					      if (err) {
 | 
				
			||||||
         /* eslint-disable no-console */
 | 
					        /* eslint-disable no-console */
 | 
				
			||||||
         console.log(err);
 | 
					        console.log(err);
 | 
				
			||||||
         reject(Error('Enable to add torrent', err)) 
 | 
					        reject(Error("Enable to add torrent", err));
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
      /* eslint-disable no-console */
 | 
					      /* eslint-disable no-console */
 | 
				
			||||||
      console.log('result/error:', err, results);
 | 
					      console.log("result/error:", err, results);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      database = establishedDatabase;
 | 
					      database = establishedDatabase;
 | 
				
			||||||
      insert_query = "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) \
 | 
					      insert_query =
 | 
				
			||||||
 | 
					        "INSERT INTO requested_torrent(magnet,torrent_name,tmdb_id) \
 | 
				
			||||||
         VALUES (?,?,?)";
 | 
					         VALUES (?,?,?)";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      let response = database.run(insert_query, [magnet, name, tmdb_id]);
 | 
					      let response = database.run(insert_query, [magnet, name, tmdb_id]);
 | 
				
			||||||
      console.log('Response from requsted_torrent insert: ' + response);
 | 
					      console.log("Response from requsted_torrent insert: " + response);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      resolve({ success: true });
 | 
					      resolve({ success: true });
 | 
				
			||||||
   }));
 | 
					    })
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = { SearchPiratebay, AddMagnet };
 | 
					module.exports = { SearchPiratebay, AddMagnet };
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,89 +1,240 @@
 | 
				
			|||||||
const fetch = require('node-fetch')
 | 
					const fetch = require("node-fetch");
 | 
				
			||||||
const convertPlexToMovie = require('src/plex/convertPlexToMovie')
 | 
					const convertPlexToMovie = require("src/plex/convertPlexToMovie");
 | 
				
			||||||
const convertPlexToShow = require('src/plex/convertPlexToShow')
 | 
					const convertPlexToShow = require("src/plex/convertPlexToShow");
 | 
				
			||||||
const convertPlexToEpisode = require('src/plex/convertPlexToEpisode')
 | 
					const convertPlexToEpisode = require("src/plex/convertPlexToEpisode");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const { Movie, Show, Person } = require("src/tmdb/types");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { Movie, Show, Person } = require('src/tmdb/types');
 | 
					const RedisCache = require("src/cache/redis");
 | 
				
			||||||
 | 
					const redisCache = new RedisCache();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// const { Movie, } 
 | 
					const sanitize = string => string.toLowerCase().replace(/[^\w]/gi, "");
 | 
				
			||||||
// TODO? import class definitions to compare types ?
 | 
					
 | 
				
			||||||
// what would typescript do?
 | 
					function fixedEncodeURIComponent(str) {
 | 
				
			||||||
 | 
					  return encodeURIComponent(str).replace(/[!'()*]/g, function (c) {
 | 
				
			||||||
 | 
					    return "%" + c.charCodeAt(0).toString(16).toUpperCase();
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const matchingTitleAndYear = (plex, tmdb) => {
 | 
				
			||||||
 | 
					  let matchingTitle, matchingYear;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (plex["title"] != null && tmdb["title"] != null) {
 | 
				
			||||||
 | 
					    const plexTitle = sanitize(plex.title);
 | 
				
			||||||
 | 
					    const tmdbTitle = sanitize(tmdb.title);
 | 
				
			||||||
 | 
					    matchingTitle = plexTitle == tmdbTitle;
 | 
				
			||||||
 | 
					    matchingTitle = matchingTitle
 | 
				
			||||||
 | 
					      ? matchingTitle
 | 
				
			||||||
 | 
					      : plexTitle.startsWith(tmdbTitle);
 | 
				
			||||||
 | 
					  } else matchingTitle = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (plex["year"] != null && tmdb["year"] != null)
 | 
				
			||||||
 | 
					    matchingYear = plex.year == tmdb.year;
 | 
				
			||||||
 | 
					  else matchingYear = false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return matchingTitle && matchingYear;
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const successfullResponse = response => {
 | 
				
			||||||
 | 
					  if (response && response["MediaContainer"]) return response;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (
 | 
				
			||||||
 | 
					    response == null ||
 | 
				
			||||||
 | 
					    response["status"] == null ||
 | 
				
			||||||
 | 
					    response["statusText"] == null
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
 | 
					    throw Error("Unable to decode response");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  const { status, statusText } = response;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (status === 200) {
 | 
				
			||||||
 | 
					    return response.json();
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    throw { message: statusText, status: status };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Plex {
 | 
					class Plex {
 | 
				
			||||||
  constructor(ip, port=32400) {
 | 
					  constructor(ip, port = 32400, cache = null) {
 | 
				
			||||||
    this.plexIP = ip
 | 
					    this.plexIP = ip;
 | 
				
			||||||
    this.plexPort = port
 | 
					    this.plexPort = port;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.cache = cache || redisCache;
 | 
				
			||||||
 | 
					    this.cacheTags = {
 | 
				
			||||||
 | 
					      machineInfo: "plex/mi",
 | 
				
			||||||
 | 
					      search: "plex/s"
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  fetchMachineIdentifier() {
 | 
				
			||||||
 | 
					    const cacheKey = `${this.cacheTags.machineInfo}`;
 | 
				
			||||||
 | 
					    const url = `http://${this.plexIP}:${this.plexPort}/`;
 | 
				
			||||||
 | 
					    const options = {
 | 
				
			||||||
 | 
					      timeout: 20000,
 | 
				
			||||||
 | 
					      headers: { Accept: "application/json" }
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return new Promise((resolve, reject) =>
 | 
				
			||||||
 | 
					      this.cache
 | 
				
			||||||
 | 
					        .get(cacheKey)
 | 
				
			||||||
 | 
					        .then(machineInfo => resolve(machineInfo["machineIdentifier"]))
 | 
				
			||||||
 | 
					        .catch(() => fetch(url, options))
 | 
				
			||||||
 | 
					        .then(response => response.json())
 | 
				
			||||||
 | 
					        .then(machineInfo =>
 | 
				
			||||||
 | 
					          this.cache.set(cacheKey, machineInfo["MediaContainer"], 2628000)
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					        .then(machineInfo => resolve(machineInfo["machineIdentifier"]))
 | 
				
			||||||
 | 
					        .catch(error => {
 | 
				
			||||||
 | 
					          if (error != undefined && error.type === "request-timeout") {
 | 
				
			||||||
 | 
					            reject({
 | 
				
			||||||
 | 
					              message: "Plex did not respond",
 | 
				
			||||||
 | 
					              status: 408,
 | 
				
			||||||
 | 
					              success: false
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          reject(error);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  matchTmdbAndPlexMedia(plex, tmdb) {
 | 
					  matchTmdbAndPlexMedia(plex, tmdb) {
 | 
				
			||||||
    if (plex === undefined || tmdb === undefined)
 | 
					    let match;
 | 
				
			||||||
      return false
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const sanitize = (string) => string.toLowerCase()
 | 
					    if (plex == null || tmdb == null) return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const matchTitle = sanitize(plex.title) === sanitize(tmdb.title)
 | 
					    if (plex instanceof Array) {
 | 
				
			||||||
    const matchYear = plex.year === tmdb.year
 | 
					      let possibleMatches = plex.map(plexItem =>
 | 
				
			||||||
 | 
					        matchingTitleAndYear(plexItem, tmdb)
 | 
				
			||||||
    return matchTitle && matchYear
 | 
					      );
 | 
				
			||||||
  }
 | 
					      match = possibleMatches.includes(true);
 | 
				
			||||||
 | 
					 | 
				
			||||||
  existsInPlex(tmdbMovie) {
 | 
					 | 
				
			||||||
    return this.search(tmdbMovie.title)
 | 
					 | 
				
			||||||
      .then(plexMovies => plexMovies.some(plex => this.matchTmdbAndPlexMedia(plex, tmdbMovie)))
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  successfullResponse(response) {
 | 
					 | 
				
			||||||
    const { status, statusText } = response
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if (status === 200) {
 | 
					 | 
				
			||||||
      return response.json()
 | 
					 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      throw { message: statusText, status: status }
 | 
					      match = matchingTitleAndYear(plex, tmdb);
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return match;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  async existsInPlex(tmdb) {
 | 
				
			||||||
 | 
					    const plexMatch = await this.findPlexItemByTitleAndYear(
 | 
				
			||||||
 | 
					      tmdb.title,
 | 
				
			||||||
 | 
					      tmdb.year
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    return plexMatch ? true : false;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  findPlexItemByTitleAndYear(title, year) {
 | 
				
			||||||
 | 
					    const query = { title, year };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this.search(title).then(plexResults => {
 | 
				
			||||||
 | 
					      const matchesInPlex = plexResults.map(plex =>
 | 
				
			||||||
 | 
					        this.matchTmdbAndPlexMedia(plex, query)
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      const matchesIndex = matchesInPlex.findIndex(el => el === true);
 | 
				
			||||||
 | 
					      return matchesInPlex != -1 ? plexResults[matchesIndex] : null;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getDirectLinkByTitleAndYear(title, year) {
 | 
				
			||||||
 | 
					    const machineIdentifierPromise = this.fetchMachineIdentifier();
 | 
				
			||||||
 | 
					    const matchingObjectInPlexPromise = this.findPlexItemByTitleAndYear(
 | 
				
			||||||
 | 
					      title,
 | 
				
			||||||
 | 
					      year
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return Promise.all([
 | 
				
			||||||
 | 
					      machineIdentifierPromise,
 | 
				
			||||||
 | 
					      matchingObjectInPlexPromise
 | 
				
			||||||
 | 
					    ]).then(([machineIdentifier, matchingObjectInPlex]) => {
 | 
				
			||||||
 | 
					      if (
 | 
				
			||||||
 | 
					        matchingObjectInPlex == false ||
 | 
				
			||||||
 | 
					        matchingObjectInPlex == null ||
 | 
				
			||||||
 | 
					        matchingObjectInPlex["key"] == null ||
 | 
				
			||||||
 | 
					        machineIdentifier == null
 | 
				
			||||||
 | 
					      )
 | 
				
			||||||
 | 
					        return false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      const keyUriComponent = fixedEncodeURIComponent(matchingObjectInPlex.key);
 | 
				
			||||||
 | 
					      return `https://app.plex.tv/desktop#!/server/${machineIdentifier}/details?key=${keyUriComponent}`;
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  search(query) {
 | 
					  search(query) {
 | 
				
			||||||
    const url = `http://${this.plexIP}:${this.plexPort}/hubs/search?query=${query}`
 | 
					    const cacheKey = `${this.cacheTags.search}:${query}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const url = `http://${this.plexIP}:${
 | 
				
			||||||
 | 
					      this.plexPort
 | 
				
			||||||
 | 
					    }/hubs/search?query=${fixedEncodeURIComponent(query)}`;
 | 
				
			||||||
    const options = {
 | 
					    const options = {
 | 
				
			||||||
      timeout: 2000,
 | 
					      timeout: 20000,
 | 
				
			||||||
      headers: { 'Accept': 'application/json' }
 | 
					      headers: { Accept: "application/json" }
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return fetch(url, options)
 | 
					    return new Promise((resolve, reject) =>
 | 
				
			||||||
      .then(this.successfullResponse)
 | 
					      this.cache
 | 
				
			||||||
      .then(this.mapResults)
 | 
					        .get(cacheKey)
 | 
				
			||||||
      .catch(error => {
 | 
					        .catch(() => fetch(url, options)) // else fetch fresh data
 | 
				
			||||||
        if (error.type === 'request-timeout') {
 | 
					        .then(successfullResponse)
 | 
				
			||||||
          throw { message: 'Plex did not respond', status: 408, success: false }
 | 
					        .then(results => this.cache.set(cacheKey, results, 21600)) // 6 hours
 | 
				
			||||||
        }
 | 
					        .then(this.mapResults)
 | 
				
			||||||
 | 
					        .then(resolve)
 | 
				
			||||||
 | 
					        .catch(error => {
 | 
				
			||||||
 | 
					          if (error != undefined && error.type === "request-timeout") {
 | 
				
			||||||
 | 
					            reject({
 | 
				
			||||||
 | 
					              message: "Plex did not respond",
 | 
				
			||||||
 | 
					              status: 408,
 | 
				
			||||||
 | 
					              success: false
 | 
				
			||||||
 | 
					            });
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        throw error
 | 
					          reject(error);
 | 
				
			||||||
 | 
					        })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  // this is not guarenteed to work, but if we see a movie or
 | 
				
			||||||
 | 
					  // show has been imported, this function can be helpfull to call
 | 
				
			||||||
 | 
					  // in order to try bust the cache preventing movieInfo and
 | 
				
			||||||
 | 
					  // showInfo from seeing updates through existsInPlex.
 | 
				
			||||||
 | 
					  bustSearchCacheWithTitle(title) {
 | 
				
			||||||
 | 
					    const query = title;
 | 
				
			||||||
 | 
					    const cacheKey = `${this.cacheTags.search}/${query}*`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.cache.del(
 | 
				
			||||||
 | 
					      cacheKey,
 | 
				
			||||||
 | 
					      (error,
 | 
				
			||||||
 | 
					      response => {
 | 
				
			||||||
 | 
					        if (response == 1) return true;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        // TODO improve cache key matching by lowercasing it on the backend.
 | 
				
			||||||
 | 
					        // what do we actually need to check for if the key was deleted or not
 | 
				
			||||||
 | 
					        // it might be an error or another response code.
 | 
				
			||||||
 | 
					        console.log("Unable to delete, key might not exists");
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  mapResults(response) {
 | 
					  mapResults(response) {
 | 
				
			||||||
    if (response === undefined || response.MediaContainer === undefined) {
 | 
					    if (
 | 
				
			||||||
      console.log('response was not valid to map', response)
 | 
					      response == null ||
 | 
				
			||||||
      return []
 | 
					      response.MediaContainer == null ||
 | 
				
			||||||
 | 
					      response.MediaContainer.Hub == null
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					      return [];
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return response.MediaContainer.Hub
 | 
					    return response.MediaContainer.Hub.filter(category => category.size > 0)
 | 
				
			||||||
      .filter(category => category.size > 0)
 | 
					 | 
				
			||||||
      .map(category => {
 | 
					      .map(category => {
 | 
				
			||||||
        if (category.type === 'movie') {
 | 
					        if (category.type === "movie") {
 | 
				
			||||||
          return category.Metadata.map(movie => {
 | 
					          return category.Metadata;
 | 
				
			||||||
            const ovie = Movie.convertFromPlexResponse(movie)
 | 
					        } else if (category.type === "show") {
 | 
				
			||||||
            return ovie.createJsonResponse()
 | 
					          return category.Metadata.map(convertPlexToShow);
 | 
				
			||||||
          })
 | 
					        } else if (category.type === "episode") {
 | 
				
			||||||
        } else if (category.type === 'show') {
 | 
					          return category.Metadata.map(convertPlexToEpisode);
 | 
				
			||||||
          return category.Metadata.map(convertPlexToShow)
 | 
					 | 
				
			||||||
        } else if (category.type === 'episode') {
 | 
					 | 
				
			||||||
          return category.Metadata.map(convertPlexToEpisode)
 | 
					 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .filter(result => result !== undefined)
 | 
					      .filter(result => result !== undefined);
 | 
				
			||||||
      .flat()
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,101 +1,130 @@
 | 
				
			|||||||
const PlexRepository = require('src/plex/plexRepository');
 | 
					const PlexRepository = require("src/plex/plexRepository");
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const TMDB = require("src/tmdb/tmdb");
 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const establishedDatabase = require("src/database/database");
 | 
				
			||||||
const establishedDatabase = require('src/database/database');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const plexRepository = new PlexRepository(configuration.get('plex', 'ip'));
 | 
					const plexRepository = new PlexRepository(configuration.get("plex", "ip"));
 | 
				
			||||||
const cache = new Cache();
 | 
					const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class RequestRepository {
 | 
					class RequestRepository {
 | 
				
			||||||
   constructor(database) {
 | 
					  constructor(database) {
 | 
				
			||||||
      this.database = database || establishedDatabase;
 | 
					    this.database = database || establishedDatabase;
 | 
				
			||||||
      this.queries = {
 | 
					    this.queries = {
 | 
				
			||||||
         insertRequest: `INSERT INTO requests(id,title,year,poster_path,background_path,requested_by,ip,user_agent,type)
 | 
					      insertRequest: `INSERT INTO requests(id,title,year,poster_path,background_path,requested_by,ip,user_agent,type)
 | 
				
			||||||
          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
 | 
					          VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,
 | 
				
			||||||
         fetchRequestedItems: 'SELECT * FROM requests ORDER BY date DESC LIMIT 25 OFFSET ?*25-25',
 | 
					      fetchRequestedItems:
 | 
				
			||||||
         fetchRequestedItemsByStatus: 'SELECT * FROM requests WHERE status IS ? AND type LIKE ? ORDER BY date DESC LIMIT 25 OFFSET ?*25-25',
 | 
					        "SELECT * FROM requests ORDER BY date DESC LIMIT 25 OFFSET ?*25-25",
 | 
				
			||||||
         updateRequestedById: 'UPDATE requests SET status = ? WHERE id is ? AND type is ?',
 | 
					      fetchRequestedItemsByStatus:
 | 
				
			||||||
         checkIfIdRequested: 'SELECT * FROM requests WHERE id IS ? AND type IS ?',
 | 
					        "SELECT * FROM requests WHERE status IS ? AND type LIKE ? ORDER BY date DESC LIMIT 25 OFFSET ?*25-25",
 | 
				
			||||||
         userRequests: 'SELECT * FROM requests WHERE requested_by IS ? ORDER BY date DESC',
 | 
					      updateRequestedById:
 | 
				
			||||||
      };
 | 
					        "UPDATE requests SET status = ? WHERE id is ? AND type is ?",
 | 
				
			||||||
      this.cacheTags = {
 | 
					      checkIfIdRequested: "SELECT * FROM requests WHERE id IS ? AND type IS ?",
 | 
				
			||||||
         search: 'se',
 | 
					      userRequests:
 | 
				
			||||||
         lookup: 'i',
 | 
					        "SELECT * FROM requests WHERE requested_by IS ? ORDER BY date DESC"
 | 
				
			||||||
      };
 | 
					    };
 | 
				
			||||||
   }
 | 
					    this.cacheTags = {
 | 
				
			||||||
 | 
					      search: "se",
 | 
				
			||||||
 | 
					      lookup: "i"
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   search(query, type, page) {
 | 
					  search(query, type, page) {
 | 
				
			||||||
      return Promise.resolve()
 | 
					    return Promise.resolve()
 | 
				
			||||||
         .then(() => tmdb.search(query, type, page))
 | 
					      .then(() => tmdb.search(query, type, page))
 | 
				
			||||||
         .catch(error => Error(`error in the house${error}`));
 | 
					      .catch(error => Error(`error in the house${error}`));
 | 
				
			||||||
   }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   lookup(identifier, type = 'movie') {
 | 
					  lookup(identifier, type = "movie") {
 | 
				
			||||||
      return Promise.resolve()
 | 
					    return Promise.resolve()
 | 
				
			||||||
         .then(() => tmdb.lookup(identifier, type))
 | 
					      .then(() => tmdb.lookup(identifier, type))
 | 
				
			||||||
         .then(tmdbMovie => this.checkID(tmdbMovie))
 | 
					      .then(tmdbMovie => this.checkID(tmdbMovie))
 | 
				
			||||||
         .then(tmdbMovie => plexRepository.inPlex(tmdbMovie))
 | 
					      .then(tmdbMovie => plexRepository.inPlex(tmdbMovie))
 | 
				
			||||||
         .catch((error) => {
 | 
					      .catch(error => {
 | 
				
			||||||
            throw new Error(error);
 | 
					        throw new Error(error);
 | 
				
			||||||
         });
 | 
					      });
 | 
				
			||||||
   }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   checkID(tmdbMovie) {
 | 
					  checkID(tmdbMovie) {
 | 
				
			||||||
      return Promise.resolve()
 | 
					    return Promise.resolve()
 | 
				
			||||||
         .then(() => this.database.get(this.queries.checkIfIdRequested, [tmdbMovie.id, tmdbMovie.type]))
 | 
					      .then(() =>
 | 
				
			||||||
         .then((result, error) => {
 | 
					        this.database.get(this.queries.checkIfIdRequested, [
 | 
				
			||||||
            if (error) { throw new Error(error); }
 | 
					          tmdbMovie.id,
 | 
				
			||||||
            tmdbMovie.requested = result ? true : false;
 | 
					          tmdbMovie.type
 | 
				
			||||||
            return tmdbMovie;
 | 
					        ])
 | 
				
			||||||
         });
 | 
					      )
 | 
				
			||||||
   }
 | 
					      .then((result, error) => {
 | 
				
			||||||
 | 
					        if (error) {
 | 
				
			||||||
 | 
					          throw new Error(error);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        tmdbMovie.requested = result ? true : false;
 | 
				
			||||||
 | 
					        return tmdbMovie;
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   /**
 | 
					  /**
 | 
				
			||||||
   * Send request for given media id.
 | 
					   * Send request for given media id.
 | 
				
			||||||
   * @param {identifier, type} the id of the media object and type of media must be defined
 | 
					   * @param {identifier, type} the id of the media object and type of media must be defined
 | 
				
			||||||
   * @returns {Promise} If nothing has gone wrong.
 | 
					   * @returns {Promise} If nothing has gone wrong.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
   sendRequest(identifier, type, ip, user_agent, user) {
 | 
					  sendRequest(identifier, type, ip, user_agent, user) {
 | 
				
			||||||
   	return Promise.resolve()
 | 
					    return Promise.resolve()
 | 
				
			||||||
   	.then(() => tmdb.lookup(identifier, type))
 | 
					      .then(() => tmdb.lookup(identifier, type))
 | 
				
			||||||
      .then((movie) => {
 | 
					      .then(movie => {
 | 
				
			||||||
         const username = user === undefined ? undefined : user.username;
 | 
					        const username = user === undefined ? undefined : user.username;
 | 
				
			||||||
         // Add request to database
 | 
					        // Add request to database
 | 
				
			||||||
         return this.database.run(this.queries.insertRequest, [movie.id, movie.title, movie.year, movie.poster_path, movie.background_path, username, ip, user_agent, movie.type]);
 | 
					        return this.database.run(this.queries.insertRequest, [
 | 
				
			||||||
 | 
					          movie.id,
 | 
				
			||||||
 | 
					          movie.title,
 | 
				
			||||||
 | 
					          movie.year,
 | 
				
			||||||
 | 
					          movie.poster_path,
 | 
				
			||||||
 | 
					          movie.background_path,
 | 
				
			||||||
 | 
					          username,
 | 
				
			||||||
 | 
					          ip,
 | 
				
			||||||
 | 
					          user_agent,
 | 
				
			||||||
 | 
					          movie.type
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
   }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   fetchRequested(status, page = '1', type = '%') {
 | 
					  fetchRequested(status, page = "1", type = "%") {
 | 
				
			||||||
   	return Promise.resolve()
 | 
					    return Promise.resolve().then(() => {
 | 
				
			||||||
   	.then(() => {
 | 
					      if (
 | 
				
			||||||
	      if (status === 'requested' || status === 'downloading' || status === 'downloaded')
 | 
					        status === "requested" ||
 | 
				
			||||||
	         return this.database.all(this.queries.fetchRequestedItemsByStatus, [status, type, page]);
 | 
					        status === "downloading" ||
 | 
				
			||||||
	      else
 | 
					        status === "downloaded"
 | 
				
			||||||
	         return this.database.all(this.queries.fetchRequestedItems, page);
 | 
					      )
 | 
				
			||||||
   	})
 | 
					        return this.database.all(this.queries.fetchRequestedItemsByStatus, [
 | 
				
			||||||
   }
 | 
					          status,
 | 
				
			||||||
 | 
					          type,
 | 
				
			||||||
 | 
					          page
 | 
				
			||||||
 | 
					        ]);
 | 
				
			||||||
 | 
					      else return this.database.all(this.queries.fetchRequestedItems, page);
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   userRequests(user) {
 | 
					  userRequests(username) {
 | 
				
			||||||
      return Promise.resolve()
 | 
					    return Promise.resolve()
 | 
				
			||||||
         .then(() => this.database.all(this.queries.userRequests, user.username))
 | 
					      .then(() => this.database.all(this.queries.userRequests, username))
 | 
				
			||||||
         .catch((error) => {
 | 
					      .catch(error => {
 | 
				
			||||||
            if (String(error).includes('no such column')) {
 | 
					        if (String(error).includes("no such column")) {
 | 
				
			||||||
               throw new Error('Username not found');
 | 
					          throw new Error("Username not found");
 | 
				
			||||||
            }
 | 
					        }
 | 
				
			||||||
            throw new Error('Unable to fetch your requests');
 | 
					        throw new Error("Unable to fetch your requests");
 | 
				
			||||||
         })
 | 
					      })
 | 
				
			||||||
         .then((result) => {
 | 
					      .then(result => {
 | 
				
			||||||
            // TODO do a correct mapping before sending, not just a dump of the database
 | 
					        // TODO do a correct mapping before sending, not just a dump of the database
 | 
				
			||||||
            result.map(item => item.poster = item.poster_path)
 | 
					        result.map(item => (item.poster = item.poster_path));
 | 
				
			||||||
            return result
 | 
					        return result;
 | 
				
			||||||
         });
 | 
					      });
 | 
				
			||||||
   }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   updateRequestedById(id, type, status) {
 | 
					  updateRequestedById(id, type, status) {
 | 
				
			||||||
      return this.database.run(this.queries.updateRequestedById, [status, id, type]);
 | 
					    return this.database.run(this.queries.updateRequestedById, [
 | 
				
			||||||
   }
 | 
					      status,
 | 
				
			||||||
 | 
					      id,
 | 
				
			||||||
 | 
					      type
 | 
				
			||||||
 | 
					    ]);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = RequestRepository;
 | 
					module.exports = RequestRepository;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,39 +0,0 @@
 | 
				
			|||||||
const Plex = require('src/plex/plex')
 | 
					 | 
				
			||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					 | 
				
			||||||
const plex = new Plex(configuration.get('plex', 'ip'))
 | 
					 | 
				
			||||||
const establishedDatabase = require('src/database/database'); 
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
class UpdateRequestsInPlex {
 | 
					 | 
				
			||||||
  constructor() {
 | 
					 | 
				
			||||||
     this.database = establishedDatabase;
 | 
					 | 
				
			||||||
     this.queries = {
 | 
					 | 
				
			||||||
        getMovies: `SELECT * FROM requests WHERE status = 'requested' OR status = 'downloading'`,
 | 
					 | 
				
			||||||
//         getMovies: "select * from requests where status is 'reset'",
 | 
					 | 
				
			||||||
        saveNewStatus: `UPDATE requests SET status = ? WHERE id IS ? and type IS ?`,
 | 
					 | 
				
			||||||
     }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  getByStatus() {
 | 
					 | 
				
			||||||
     return this.database.all(this.queries.getMovies);
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  scrub() {
 | 
					 | 
				
			||||||
     return this.getByStatus()
 | 
					 | 
				
			||||||
        .then((requests) => Promise.all(requests.map(movie => plex.existsInPlex(movie))))
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  commitNewStatus(status, id, type, title) {
 | 
					 | 
				
			||||||
    console.log(type, title, 'updated to:', status)
 | 
					 | 
				
			||||||
    this.database.run(this.queries.saveNewStatus, [status, id, type])
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   
 | 
					 | 
				
			||||||
  updateStatus(status) {
 | 
					 | 
				
			||||||
    this.getByStatus()
 | 
					 | 
				
			||||||
      .then(requests => Promise.all(requests.map(request => plex.existsInPlex(request))))
 | 
					 | 
				
			||||||
      .then(matchedRequests => matchedRequests.filter(request => request.existsInPlex))
 | 
					 | 
				
			||||||
      .then(newMatches => newMatches.map(match => this.commitNewStatus(status, match.id, match.type, match.title)))
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
var requestsUpdater = new UpdateRequestsInPlex();
 | 
					 | 
				
			||||||
requestsUpdater.updateStatus('downloaded')
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = UpdateRequestsInPlex
 | 
					 | 
				
			||||||
@@ -1,9 +1,7 @@
 | 
				
			|||||||
const assert = require('assert')
 | 
					const assert = require('assert')
 | 
				
			||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require('src/config/configuration').getInstance();
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const TMDB = require('src/tmdb/tmdb');
 | 
				
			||||||
const cache = new Cache();
 | 
					const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					 | 
				
			||||||
const establishedDatabase = require('src/database/database');
 | 
					const establishedDatabase = require('src/database/database');
 | 
				
			||||||
const utils = require('./utils');
 | 
					const utils = require('./utils');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
const fetch = require('node-fetch');
 | 
					const fetch = require("node-fetch");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Tautulli {
 | 
					class Tautulli {
 | 
				
			||||||
  constructor(apiKey, ip, port) {
 | 
					  constructor(apiKey, ip, port) {
 | 
				
			||||||
@@ -8,50 +8,66 @@ class Tautulli {
 | 
				
			|||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  buildUrlWithCmdAndUserid(cmd, user_id) {
 | 
					  buildUrlWithCmdAndUserid(cmd, user_id) {
 | 
				
			||||||
    const url = new URL('api/v2', `http://${this.ip}:${this.port}`)
 | 
					    const url = new URL("api/v2", `http://${this.ip}:${this.port}`);
 | 
				
			||||||
    url.searchParams.append('apikey', this.apiKey)
 | 
					    url.searchParams.append("apikey", this.apiKey);
 | 
				
			||||||
    url.searchParams.append('cmd', cmd)
 | 
					    url.searchParams.append("cmd", cmd);
 | 
				
			||||||
    url.searchParams.append('user_id', user_id)
 | 
					    url.searchParams.append("user_id", user_id);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return url
 | 
					    return url;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  logTautulliError(error) {
 | 
				
			||||||
 | 
					    console.error("error fetching from tautulli");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    throw error;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getPlaysByDayOfWeek(plex_userid, days, y_axis) {
 | 
					  getPlaysByDayOfWeek(plex_userid, days, y_axis) {
 | 
				
			||||||
    const url = this.buildUrlWithCmdAndUserid('get_plays_by_dayofweek', plex_userid)
 | 
					    const url = this.buildUrlWithCmdAndUserid(
 | 
				
			||||||
    url.searchParams.append('time_range', days)
 | 
					      "get_plays_by_dayofweek",
 | 
				
			||||||
    url.searchParams.append('y_axis', y_axis)
 | 
					      plex_userid
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    url.searchParams.append("time_range", days);
 | 
				
			||||||
 | 
					    url.searchParams.append("y_axis", y_axis);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return fetch(url.href)
 | 
					    return fetch(url.href)
 | 
				
			||||||
      .then(resp => resp.json())
 | 
					      .then(resp => resp.json())
 | 
				
			||||||
 | 
					      .catch(error => this.logTautulliError(error));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  getPlaysByDays(plex_userid, days, y_axis) {
 | 
					  getPlaysByDays(plex_userid, days, y_axis) {
 | 
				
			||||||
    const url = this.buildUrlWithCmdAndUserid('get_plays_by_date', plex_userid)
 | 
					    const url = this.buildUrlWithCmdAndUserid("get_plays_by_date", plex_userid);
 | 
				
			||||||
    url.searchParams.append('time_range', days)
 | 
					    url.searchParams.append("time_range", days);
 | 
				
			||||||
    url.searchParams.append('y_axis', y_axis)
 | 
					    url.searchParams.append("y_axis", y_axis);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return fetch(url.href)
 | 
					    return fetch(url.href)
 | 
				
			||||||
      .then(resp => resp.json())
 | 
					      .then(resp => resp.json())
 | 
				
			||||||
 | 
					      .catch(error => this.logTautulliError(error));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  watchTimeStats(plex_userid) {
 | 
					  watchTimeStats(plex_userid) {
 | 
				
			||||||
    const url = this.buildUrlWithCmdAndUserid('get_user_watch_time_stats', plex_userid)
 | 
					    const url = this.buildUrlWithCmdAndUserid(
 | 
				
			||||||
    url.searchParams.append('grouping', 0)
 | 
					      "get_user_watch_time_stats",
 | 
				
			||||||
 | 
					      plex_userid
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    url.searchParams.append("grouping", 0);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return fetch(url.href)
 | 
					    return fetch(url.href)
 | 
				
			||||||
      .then(resp => resp.json())
 | 
					      .then(resp => resp.json())
 | 
				
			||||||
}
 | 
					      .catch(error => this.logTautulliError(error));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  viewHistory(plex_userid) {
 | 
					  viewHistory(plex_userid) {
 | 
				
			||||||
    const url = this.buildUrlWithCmdAndUserid('get_history', plex_userid)
 | 
					    const url = this.buildUrlWithCmdAndUserid("get_history", plex_userid);
 | 
				
			||||||
    
 | 
					 | 
				
			||||||
    url.searchParams.append('start', 0)
 | 
					 | 
				
			||||||
    url.searchParams.append('length', 50)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    console.log('fetching url', url.href)
 | 
					    url.searchParams.append("start", 0);
 | 
				
			||||||
 | 
					    url.searchParams.append("length", 50);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    console.log("fetching url", url.href);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return fetch(url.href)
 | 
					    return fetch(url.href)
 | 
				
			||||||
      .then(resp => resp.json())
 | 
					      .then(resp => resp.json())
 | 
				
			||||||
 | 
					      .catch(error => this.logTautulliError(error));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,31 +1,74 @@
 | 
				
			|||||||
const moviedb = require('km-moviedb');
 | 
					const moviedb = require("km-moviedb");
 | 
				
			||||||
 | 
					const RedisCache = require("src/cache/redis");
 | 
				
			||||||
 | 
					const redisCache = new RedisCache();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const { Movie, Show, Person, Credits, ReleaseDates } = require('src/tmdb/types');
 | 
					const {
 | 
				
			||||||
// const { tmdbInfo } = require('src/tmdb/types')
 | 
					  Movie,
 | 
				
			||||||
 | 
					  Show,
 | 
				
			||||||
 | 
					  Person,
 | 
				
			||||||
 | 
					  Credits,
 | 
				
			||||||
 | 
					  ReleaseDates
 | 
				
			||||||
 | 
					} = require("src/tmdb/types");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const tmdbErrorResponse = (error, typeString = undefined) => {
 | 
				
			||||||
 | 
					  if (error.status === 404) {
 | 
				
			||||||
 | 
					    let message = error.response.body.status_message;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    throw {
 | 
				
			||||||
 | 
					      status: 404,
 | 
				
			||||||
 | 
					      message: message.slice(0, -1) + " in tmdb."
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  } else if (error.status === 401) {
 | 
				
			||||||
 | 
					    throw {
 | 
				
			||||||
 | 
					      status: 401,
 | 
				
			||||||
 | 
					      message: error.response.body.status_message
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  throw {
 | 
				
			||||||
 | 
					    status: 500,
 | 
				
			||||||
 | 
					    message: `An unexpected error occured while fetching ${typeString} from tmdb`
 | 
				
			||||||
 | 
					  };
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class TMDB {
 | 
					class TMDB {
 | 
				
			||||||
  constructor(cache, apiKey, tmdbLibrary) {
 | 
					  constructor(apiKey, cache, tmdbLibrary) {
 | 
				
			||||||
    this.cache = cache;
 | 
					 | 
				
			||||||
    this.tmdbLibrary = tmdbLibrary || moviedb(apiKey);
 | 
					    this.tmdbLibrary = tmdbLibrary || moviedb(apiKey);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    this.cache = cache || redisCache;
 | 
				
			||||||
    this.cacheTags = {
 | 
					    this.cacheTags = {
 | 
				
			||||||
      multiSearch: 'mus', 
 | 
					      multiSearch: "mus",
 | 
				
			||||||
      movieSearch: 'mos', 
 | 
					      movieSearch: "mos",
 | 
				
			||||||
      showSearch: 'ss',
 | 
					      showSearch: "ss",
 | 
				
			||||||
      personSearch: 'ps',
 | 
					      personSearch: "ps",
 | 
				
			||||||
      movieInfo: 'mi',
 | 
					      movieInfo: "mi",
 | 
				
			||||||
      movieCredits: 'mc',
 | 
					      movieCredits: "mc",
 | 
				
			||||||
      movieReleaseDates: 'mrd',
 | 
					      movieReleaseDates: "mrd",
 | 
				
			||||||
      showInfo: 'si',
 | 
					      movieImages: "mimg",
 | 
				
			||||||
      showCredits: 'sc',
 | 
					      showInfo: "si",
 | 
				
			||||||
      personInfo: 'pi',
 | 
					      showCredits: "sc",
 | 
				
			||||||
      miscNowPlayingMovies: 'npm',
 | 
					      personInfo: "pi",
 | 
				
			||||||
      miscPopularMovies: 'pm',
 | 
					      personCredits: "pc",
 | 
				
			||||||
      miscTopRatedMovies: 'tpm',
 | 
					      miscNowPlayingMovies: "npm",
 | 
				
			||||||
      miscUpcomingMovies: 'um',
 | 
					      miscPopularMovies: "pm",
 | 
				
			||||||
      tvOnTheAir: 'toa',
 | 
					      miscTopRatedMovies: "tpm",
 | 
				
			||||||
      miscPopularTvs: 'pt',
 | 
					      miscUpcomingMovies: "um",
 | 
				
			||||||
      miscTopRatedTvs: 'trt',
 | 
					      tvOnTheAir: "toa",
 | 
				
			||||||
 | 
					      miscPopularTvs: "pt",
 | 
				
			||||||
 | 
					      miscTopRatedTvs: "trt"
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
 | 
					    this.defaultTTL = 86400;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  getFromCacheOrFetchFromTmdb(cacheKey, tmdbMethod, query) {
 | 
				
			||||||
 | 
					    return new Promise((resolve, reject) =>
 | 
				
			||||||
 | 
					      this.cache
 | 
				
			||||||
 | 
					        .get(cacheKey)
 | 
				
			||||||
 | 
					        .then(resolve)
 | 
				
			||||||
 | 
					        .catch(() => this.tmdb(tmdbMethod, query))
 | 
				
			||||||
 | 
					        .then(resolve)
 | 
				
			||||||
 | 
					        .catch(error => reject(tmdbErrorResponse(error, tmdbMethod)))
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -37,13 +80,11 @@ class TMDB {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  movieInfo(identifier) {
 | 
					  movieInfo(identifier) {
 | 
				
			||||||
    const query = { id: identifier };
 | 
					    const query = { id: identifier };
 | 
				
			||||||
    const cacheKey = `${this.cacheTags.movieInfo}:${identifier}`;
 | 
					    const cacheKey = `tmdb/${this.cacheTags.movieInfo}:${identifier}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.cache.get(cacheKey)
 | 
					    return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieInfo", query)
 | 
				
			||||||
      .catch(() => this.tmdb('movieInfo', query))
 | 
					      .then(movie => this.cache.set(cacheKey, movie, this.defaultTTL))
 | 
				
			||||||
      .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie info'))
 | 
					      .then(movie => Movie.convertFromTmdbResponse(movie));
 | 
				
			||||||
      .then(movie => this.cache.set(cacheKey, movie, 1))
 | 
					 | 
				
			||||||
      .then(movie => Movie.convertFromTmdbResponse(movie))
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -52,14 +93,12 @@ class TMDB {
 | 
				
			|||||||
   * @returns {Promise} movie cast object
 | 
					   * @returns {Promise} movie cast object
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  movieCredits(identifier) {
 | 
					  movieCredits(identifier) {
 | 
				
			||||||
    const query = { id: identifier }
 | 
					    const query = { id: identifier };
 | 
				
			||||||
    const cacheKey = `${this.cacheTags.movieCredits}:${identifier}`
 | 
					    const cacheKey = `tmdb/${this.cacheTags.movieCredits}:${identifier}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.cache.get(cacheKey)
 | 
					    return this.getFromCacheOrFetchFromTmdb(cacheKey, "movieCredits", query)
 | 
				
			||||||
      .catch(() => this.tmdb('movieCredits', query))
 | 
					      .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL))
 | 
				
			||||||
      .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie credits'))
 | 
					      .then(credits => Credits.convertFromTmdbResponse(credits));
 | 
				
			||||||
      .then(credits => this.cache.set(cacheKey, credits, 1))
 | 
					 | 
				
			||||||
      .then(credits => Credits.convertFromTmdbResponse(credits))
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -69,12 +108,10 @@ class TMDB {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  movieReleaseDates(identifier) {
 | 
					  movieReleaseDates(identifier) {
 | 
				
			||||||
    const query = { id: identifier }
 | 
					    const query = { id: identifier }
 | 
				
			||||||
    const cacheKey = `${this.cacheTags.movieReleaseDates}:${identifier}`
 | 
					    const cacheKey = `tmdb/${this.cacheTags.movieReleaseDates}:${identifier}`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.cache.get(cacheKey)
 | 
					    return this.getFromCacheOrFetchFromTmdb(cacheKey, 'movieReleaseDates', query)
 | 
				
			||||||
      .catch(() => this.tmdb('movieReleaseDates', query))
 | 
					      .then(releaseDates => this.cache.set(cacheKey, releaseDates, this.defaultTTL))
 | 
				
			||||||
      .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie release dates'))
 | 
					 | 
				
			||||||
      .then(releaseDates => this.cache.set(cacheKey, releaseDates, 1))
 | 
					 | 
				
			||||||
      .then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates))
 | 
					      .then(releaseDates => ReleaseDates.convertFromTmdbResponse(releaseDates))
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
					 
 | 
				
			||||||
@@ -86,24 +123,20 @@ class TMDB {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  showInfo(identifier) {
 | 
					  showInfo(identifier) {
 | 
				
			||||||
    const query = { id: identifier };
 | 
					    const query = { id: identifier };
 | 
				
			||||||
    const cacheKey = `${this.cacheTags.showInfo}:${identifier}`;
 | 
					    const cacheKey = `tmdb/${this.cacheTags.showInfo}:${identifier}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.cache.get(cacheKey)
 | 
					    return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvInfo", query)
 | 
				
			||||||
      .catch(() => this.tmdb('tvInfo', query))
 | 
					      .then(show => this.cache.set(cacheKey, show, this.defaultTTL))
 | 
				
			||||||
      .catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv info'))
 | 
					      .then(show => Show.convertFromTmdbResponse(show));
 | 
				
			||||||
      .then(show => this.cache.set(cacheKey, show, 1))
 | 
					 | 
				
			||||||
      .then(show => Show.convertFromTmdbResponse(show))
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  showCredits(identifier) {
 | 
					  showCredits(identifier) {
 | 
				
			||||||
    const query = { id: identifier }
 | 
					    const query = { id: identifier };
 | 
				
			||||||
    const cacheKey = `${this.cacheTags.showCredits}:${identifier}`
 | 
					    const cacheKey = `tmdb/${this.cacheTags.showCredits}:${identifier}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.cache.get(cacheKey)
 | 
					    return this.getFromCacheOrFetchFromTmdb(cacheKey, "tvCredits", query)
 | 
				
			||||||
      .catch(() => this.tmdb('tvCredits', query))
 | 
					      .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL))
 | 
				
			||||||
      .catch(tmdbError => tmdbErrorResponse(tmdbError, 'show credits'))
 | 
					      .then(credits => Credits.convertFromTmdbResponse(credits));
 | 
				
			||||||
      .then(credits => this.cache.set(cacheKey, credits, 1))
 | 
					 | 
				
			||||||
      .then(credits => Credits.convertFromTmdbResponse(credits))
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -114,22 +147,32 @@ class TMDB {
 | 
				
			|||||||
   */
 | 
					   */
 | 
				
			||||||
  personInfo(identifier) {
 | 
					  personInfo(identifier) {
 | 
				
			||||||
    const query = { id: identifier };
 | 
					    const query = { id: identifier };
 | 
				
			||||||
    const cacheKey = `${this.cacheTags.personInfo}:${identifier}`;
 | 
					    const cacheKey = `tmdb/${this.cacheTags.personInfo}:${identifier}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.cache.get(cacheKey)
 | 
					    return this.getFromCacheOrFetchFromTmdb(cacheKey, "personInfo", query)
 | 
				
			||||||
      .catch(() => this.tmdb('personInfo', query))
 | 
					      .then(person => this.cache.set(cacheKey, person, this.defaultTTL))
 | 
				
			||||||
      .catch(tmdbError => tmdbErrorResponse(tmdbError, 'person info'))
 | 
					      .then(person => Person.convertFromTmdbResponse(person));
 | 
				
			||||||
      .then(person => this.cache.set(cacheKey, person, 1))
 | 
					 | 
				
			||||||
      .then(person => Person.convertFromTmdbResponse(person))
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  multiSearch(search_query, page=1) {
 | 
					  personCredits(identifier) {
 | 
				
			||||||
    const query = { query: search_query, page: page };
 | 
					    const query = { id: identifier };
 | 
				
			||||||
    const cacheKey = `${this.cacheTags.multiSearch}:${page}:${search_query}`;
 | 
					    const cacheKey = `tmdb/${this.cacheTags.personCredits}:${identifier}`;
 | 
				
			||||||
    return this.cache.get(cacheKey)
 | 
					
 | 
				
			||||||
      .catch(() => this.tmdb('searchMulti', query))
 | 
					    return this.getFromCacheOrFetchFromTmdb(
 | 
				
			||||||
      .catch(tmdbError => tmdbErrorResponse(tmdbError, 'search results'))
 | 
					      cacheKey,
 | 
				
			||||||
      .then(response => this.cache.set(cacheKey, response, 1))
 | 
					      "personCombinedCredits",
 | 
				
			||||||
 | 
					      query
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					      .then(credits => this.cache.set(cacheKey, credits, this.defaultTTL))
 | 
				
			||||||
 | 
					      .then(credits => Credits.convertFromTmdbResponse(credits));
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  multiSearch(search_query, page = 1, include_adult = true) {
 | 
				
			||||||
 | 
					    const query = { query: search_query, page, include_adult };
 | 
				
			||||||
 | 
					    const cacheKey = `tmdb/${this.cacheTags.multiSearch}:${page}:${search_query}:${include_adult}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMulti", query)
 | 
				
			||||||
 | 
					      .then(response => this.cache.set(cacheKey, response, this.defaultTTL))
 | 
				
			||||||
      .then(response => this.mapResults(response));
 | 
					      .then(response => this.mapResults(response));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -139,15 +182,13 @@ class TMDB {
 | 
				
			|||||||
   * @param {Number} page representing pagination of results
 | 
					   * @param {Number} page representing pagination of results
 | 
				
			||||||
   * @returns {Promise} dict with query results, current page and total_pages
 | 
					   * @returns {Promise} dict with query results, current page and total_pages
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  movieSearch(query, page=1) {
 | 
					  movieSearch(search_query, page = 1, include_adult = true) {
 | 
				
			||||||
    const tmdbquery = { query: query, page: page };
 | 
					    const tmdbquery = { query: search_query, page, include_adult };
 | 
				
			||||||
    const cacheKey = `${this.cacheTags.movieSearch}:${page}:${query}`;
 | 
					    const cacheKey = `tmdb/${this.cacheTags.movieSearch}:${page}:${search_query}:${include_adult}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.cache.get(cacheKey)
 | 
					    return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchMovie", tmdbquery)
 | 
				
			||||||
      .catch(() => this.tmdb('searchMovie', tmdbquery))
 | 
					      .then(response => this.cache.set(cacheKey, response, this.defaultTTL))
 | 
				
			||||||
      .catch(tmdbError => tmdbErrorResponse(tmdbError, 'movie search results'))
 | 
					      .then(response => this.mapResults(response, "movie"));
 | 
				
			||||||
      .then(response => this.cache.set(cacheKey, response, 1))
 | 
					 | 
				
			||||||
      .then(response => this.mapResults(response, 'movie'))
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -156,15 +197,13 @@ class TMDB {
 | 
				
			|||||||
   * @param {Number} page representing pagination of results
 | 
					   * @param {Number} page representing pagination of results
 | 
				
			||||||
   * @returns {Promise} dict with query results, current page and total_pages
 | 
					   * @returns {Promise} dict with query results, current page and total_pages
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  showSearch(query, page=1) {
 | 
					  showSearch(search_query, page = 1, include_adult = true) {
 | 
				
			||||||
    const tmdbquery = { query: query, page: page };
 | 
					    const tmdbquery = { query: search_query, page, include_adult };
 | 
				
			||||||
    const cacheKey = `${this.cacheTags.showSearch}:${page}:${query}`;
 | 
					    const cacheKey = `tmdb/${this.cacheTags.showSearch}:${page}:${search_query}:${include_adult}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.cache.get(cacheKey)
 | 
					    return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchTv", tmdbquery)
 | 
				
			||||||
      .catch(() => this.tmdb('searchTv', tmdbquery))
 | 
					      .then(response => this.cache.set(cacheKey, response, this.defaultTTL))
 | 
				
			||||||
      .catch(tmdbError => tmdbErrorResponse(tmdbError, 'tv search results'))
 | 
					      .then(response => this.mapResults(response, "show"));
 | 
				
			||||||
      .then(response => this.cache.set(cacheKey, response, 1))
 | 
					 | 
				
			||||||
      .then(response => this.mapResults(response, 'show'))
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -173,37 +212,31 @@ class TMDB {
 | 
				
			|||||||
   * @param {Number} page representing pagination of results
 | 
					   * @param {Number} page representing pagination of results
 | 
				
			||||||
   * @returns {Promise} dict with query results, current page and total_pages
 | 
					   * @returns {Promise} dict with query results, current page and total_pages
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  personSearch(query, page=1) {
 | 
					  personSearch(search_query, page = 1, include_adult = true) {
 | 
				
			||||||
 | 
					    const tmdbquery = { query: search_query, page, include_adult };
 | 
				
			||||||
 | 
					    const cacheKey = `tmdb/${this.cacheTags.personSearch}:${page}:${search_query}:${include_adult}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const tmdbquery = { query: query, page: page, include_adult: true };
 | 
					    return this.getFromCacheOrFetchFromTmdb(cacheKey, "searchPerson", tmdbquery)
 | 
				
			||||||
    const cacheKey = `${this.cacheTags.personSearch}:${page}:${query}`;
 | 
					      .then(response => this.cache.set(cacheKey, response, this.defaultTTL))
 | 
				
			||||||
 | 
					      .then(response => this.mapResults(response, "person"));
 | 
				
			||||||
    return this.cache.get(cacheKey)
 | 
					 | 
				
			||||||
      .catch(() => this.tmdb('searchPerson', tmdbquery))
 | 
					 | 
				
			||||||
      .catch(tmdbError => tmdbErrorResponse(tmdbError, 'person search results'))
 | 
					 | 
				
			||||||
      .then(response => this.cache.set(cacheKey, response, 1))
 | 
					 | 
				
			||||||
      .then(response => this.mapResults(response, 'person'))
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  movieList(listname, page = 1) {
 | 
					  movieList(listname, page = 1) {
 | 
				
			||||||
    const query = { page: page };
 | 
					    const query = { page: page };
 | 
				
			||||||
    const cacheKey = `${this.cacheTags[listname]}:${page}`;
 | 
					    const cacheKey = `tmdb/${this.cacheTags[listname]}:${page}`;
 | 
				
			||||||
    return this.cache.get(cacheKey)
 | 
					
 | 
				
			||||||
      .catch(() => this.tmdb(listname, query))
 | 
					    return this.getFromCacheOrFetchFromTmdb(cacheKey, listname, query)
 | 
				
			||||||
      .catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'movie list ' + listname))
 | 
					      .then(response => this.cache.set(cacheKey, response, this.defaultTTL))
 | 
				
			||||||
      .then(response => this.cache.set(cacheKey, response, 1))
 | 
					      .then(response => this.mapResults(response, "movie"));
 | 
				
			||||||
      .then(response => this.mapResults(response, 'movie'))
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  showList(listname, page = 1) {
 | 
					  showList(listname, page = 1) {
 | 
				
			||||||
    const query = { page: page };
 | 
					    const query = { page: page };
 | 
				
			||||||
    const cacheKey = `${this.cacheTags[listname]}:${page}`;
 | 
					    const cacheKey = `tmdb/${this.cacheTags[listname]}:${page}`;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.cache.get(cacheKey)
 | 
					    return this.getFromCacheOrFetchFromTmdb(cacheKey, listName, query)
 | 
				
			||||||
      .catch(() => this.tmdb(listname, query))
 | 
					      .then(response => this.cache.set(cacheKey, response, this.defaultTTL))
 | 
				
			||||||
      .catch(tmdbError => this.tmdbErrorResponse(tmdbError, 'show list ' + listname))
 | 
					      .then(response => this.mapResults(response, "show"));
 | 
				
			||||||
      .then(response => this.cache.set(cacheKey, response, 1))
 | 
					 | 
				
			||||||
      .then(response => this.mapResults(response, 'show'))
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -212,29 +245,26 @@ class TMDB {
 | 
				
			|||||||
   * @param {String} The type declared in listSearch.
 | 
					   * @param {String} The type declared in listSearch.
 | 
				
			||||||
   * @returns {Promise} dict with tmdb results, mapped as movie/show objects.
 | 
					   * @returns {Promise} dict with tmdb results, mapped as movie/show objects.
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  mapResults(response, type=undefined) {
 | 
					  mapResults(response, type = undefined) {
 | 
				
			||||||
    // console.log(response.results)
 | 
					 | 
				
			||||||
    // response.results.map(te => console.table(te))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    let results = response.results.map(result => {
 | 
					    let results = response.results.map(result => {
 | 
				
			||||||
      if (type === 'movie' || result.media_type === 'movie') {
 | 
					      if (type === "movie" || result.media_type === "movie") {
 | 
				
			||||||
        const movie = Movie.convertFromTmdbResponse(result)
 | 
					        const movie = Movie.convertFromTmdbResponse(result);
 | 
				
			||||||
        return movie.createJsonResponse()
 | 
					        return movie.createJsonResponse();
 | 
				
			||||||
      } else if (type === 'show' || result.media_type === 'tv') {
 | 
					      } else if (type === "show" || result.media_type === "tv") {
 | 
				
			||||||
        const show = Show.convertFromTmdbResponse(result)
 | 
					        const show = Show.convertFromTmdbResponse(result);
 | 
				
			||||||
        return show.createJsonResponse()
 | 
					        return show.createJsonResponse();
 | 
				
			||||||
      } else if (type === 'person' || result.media_type === 'person') {
 | 
					      } else if (type === "person" || result.media_type === "person") {
 | 
				
			||||||
        const person = Person.convertFromTmdbResponse(result)
 | 
					        const person = Person.convertFromTmdbResponse(result);
 | 
				
			||||||
        return person.createJsonResponse()
 | 
					        return person.createJsonResponse();
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
      results: results,
 | 
					      results: results,
 | 
				
			||||||
      page: response.page,
 | 
					      page: response.page,
 | 
				
			||||||
      total_results: response.total_results,
 | 
					      total_results: response.total_results,
 | 
				
			||||||
      total_pages: response.total_pages
 | 
					      total_pages: response.total_pages
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -243,43 +273,21 @@ class TMDB {
 | 
				
			|||||||
   * @param {Object} argument argument to function being called
 | 
					   * @param {Object} argument argument to function being called
 | 
				
			||||||
   * @returns {Promise} succeeds if callback succeeds
 | 
					   * @returns {Promise} succeeds if callback succeeds
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
   tmdb(method, argument) {
 | 
					  tmdb(method, argument) {
 | 
				
			||||||
      return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
         const callback = (error, reponse) => {
 | 
					      const callback = (error, reponse) => {
 | 
				
			||||||
            if (error) {
 | 
					        if (error) {
 | 
				
			||||||
               return reject(error);
 | 
					          return reject(error);
 | 
				
			||||||
            }
 | 
					        }
 | 
				
			||||||
            resolve(reponse);
 | 
					        resolve(reponse);
 | 
				
			||||||
         };
 | 
					      };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
         if (!argument) {
 | 
					      if (!argument) {
 | 
				
			||||||
            this.tmdbLibrary[method](callback);
 | 
					        this.tmdbLibrary[method](callback);
 | 
				
			||||||
         } else {
 | 
					      } else {
 | 
				
			||||||
            this.tmdbLibrary[method](argument, callback);
 | 
					        this.tmdbLibrary[method](argument, callback);
 | 
				
			||||||
         }
 | 
					      }
 | 
				
			||||||
      });
 | 
					    });
 | 
				
			||||||
   }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
function tmdbErrorResponse(error, typeString=undefined) {
 | 
					 | 
				
			||||||
  if (error.status === 404) {
 | 
					 | 
				
			||||||
    let message = error.response.body.status_message;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    throw {
 | 
					 | 
				
			||||||
      status: 404,
 | 
					 | 
				
			||||||
      message: message.slice(0, -1) + " in tmdb."
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  } else if (error.status === 401) {
 | 
					 | 
				
			||||||
    throw {
 | 
					 | 
				
			||||||
      status: 401,
 | 
					 | 
				
			||||||
      message: error.response.body.status_message
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  throw {
 | 
					 | 
				
			||||||
    status: 500,
 | 
					 | 
				
			||||||
    message: `An unexpected error occured while fetching ${typeString} from tmdb`
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,7 +1,7 @@
 | 
				
			|||||||
import Movie from './types/movie.js'
 | 
					const Movie = require('./types/movie.js')
 | 
				
			||||||
import Show from './types/show.js'
 | 
					const Show = require('./types/show.js')
 | 
				
			||||||
import Person from './types/person.js'
 | 
					const Person = require('./types/person.js')
 | 
				
			||||||
import Credits from './types/credits.js'
 | 
					const Credits = require('./types/credits.js')
 | 
				
			||||||
import ReleaseDates from './types/releaseDates.js'
 | 
					const ReleaseDates = require('./types/releaseDates.js')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = { Movie, Show, Person, Credits, ReleaseDates }
 | 
					module.exports = { Movie, Show, Person, Credits, ReleaseDates }
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,20 +1,55 @@
 | 
				
			|||||||
class Credits { 
 | 
					import Movie from "./movie";
 | 
				
			||||||
  constructor(id, cast=[], crew=[]) {
 | 
					import Show from "./show";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Credits {
 | 
				
			||||||
 | 
					  constructor(id, cast = [], crew = []) {
 | 
				
			||||||
    this.id = id;
 | 
					    this.id = id;
 | 
				
			||||||
    this.cast = cast;
 | 
					    this.cast = cast;
 | 
				
			||||||
    this.crew = crew;
 | 
					    this.crew = crew;
 | 
				
			||||||
    this.type = 'credits';
 | 
					    this.type = "credits";
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static convertFromTmdbResponse(response) {
 | 
					  static convertFromTmdbResponse(response) {
 | 
				
			||||||
    const { id, cast, crew } = response;
 | 
					    const { id, cast, crew } = response;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const allCast = cast.map(cast => 
 | 
					    const allCast = cast.map(cast => {
 | 
				
			||||||
      new CastMember(cast.character, cast.gender, cast.id, cast.name, cast.profile_path))
 | 
					      if (cast["media_type"]) {
 | 
				
			||||||
    const allCrew = crew.map(crew =>
 | 
					        if (cast.media_type === "movie") {
 | 
				
			||||||
      new CrewMember(crew.department, crew.gender, crew.id, crew.job, crew.name, crew.profile_path))
 | 
					          return CreditedMovie.convertFromTmdbResponse(cast);
 | 
				
			||||||
 | 
					        } else if (cast.media_type === "tv") {
 | 
				
			||||||
 | 
					          return CreditedShow.convertFromTmdbResponse(cast);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return new Credits(id, allCast, allCrew)
 | 
					      return new CastMember(
 | 
				
			||||||
 | 
					        cast.character,
 | 
				
			||||||
 | 
					        cast.gender,
 | 
				
			||||||
 | 
					        cast.id,
 | 
				
			||||||
 | 
					        cast.name,
 | 
				
			||||||
 | 
					        cast.profile_path
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const allCrew = crew.map(crew => {
 | 
				
			||||||
 | 
					      if (cast["media_type"]) {
 | 
				
			||||||
 | 
					        if (cast.media_type === "movie") {
 | 
				
			||||||
 | 
					          return CreditedMovie.convertFromTmdbResponse(cast);
 | 
				
			||||||
 | 
					        } else if (cast.media_type === "tv") {
 | 
				
			||||||
 | 
					          return CreditedShow.convertFromTmdbResponse(cast);
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      return new CrewMember(
 | 
				
			||||||
 | 
					        crew.department,
 | 
				
			||||||
 | 
					        crew.gender,
 | 
				
			||||||
 | 
					        crew.id,
 | 
				
			||||||
 | 
					        crew.job,
 | 
				
			||||||
 | 
					        crew.name,
 | 
				
			||||||
 | 
					        crew.profile_path
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return new Credits(id, allCast, allCrew);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  createJsonResponse() {
 | 
					  createJsonResponse() {
 | 
				
			||||||
@@ -22,7 +57,7 @@ class Credits {
 | 
				
			|||||||
      id: this.id,
 | 
					      id: this.id,
 | 
				
			||||||
      cast: this.cast.map(cast => cast.createJsonResponse()),
 | 
					      cast: this.cast.map(cast => cast.createJsonResponse()),
 | 
				
			||||||
      crew: this.crew.map(crew => crew.createJsonResponse())
 | 
					      crew: this.crew.map(crew => crew.createJsonResponse())
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -33,7 +68,7 @@ class CastMember {
 | 
				
			|||||||
    this.id = id;
 | 
					    this.id = id;
 | 
				
			||||||
    this.name = name;
 | 
					    this.name = name;
 | 
				
			||||||
    this.profile_path = profile_path;
 | 
					    this.profile_path = profile_path;
 | 
				
			||||||
    this.type = 'cast member';
 | 
					    this.type = "person";
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  createJsonResponse() {
 | 
					  createJsonResponse() {
 | 
				
			||||||
@@ -44,7 +79,7 @@ class CastMember {
 | 
				
			|||||||
      name: this.name,
 | 
					      name: this.name,
 | 
				
			||||||
      profile_path: this.profile_path,
 | 
					      profile_path: this.profile_path,
 | 
				
			||||||
      type: this.type
 | 
					      type: this.type
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -56,7 +91,7 @@ class CrewMember {
 | 
				
			|||||||
    this.job = job;
 | 
					    this.job = job;
 | 
				
			||||||
    this.name = name;
 | 
					    this.name = name;
 | 
				
			||||||
    this.profile_path = profile_path;
 | 
					    this.profile_path = profile_path;
 | 
				
			||||||
    this.type = 'crew member';
 | 
					    this.type = "person";
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  createJsonResponse() {
 | 
					  createJsonResponse() {
 | 
				
			||||||
@@ -68,8 +103,11 @@ class CrewMember {
 | 
				
			|||||||
      name: this.name,
 | 
					      name: this.name,
 | 
				
			||||||
      profile_path: this.profile_path,
 | 
					      profile_path: this.profile_path,
 | 
				
			||||||
      type: this.type
 | 
					      type: this.type
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreditedMovie extends Movie {}
 | 
				
			||||||
 | 
					class CreditedShow extends Show {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = Credits;
 | 
					module.exports = Credits;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,23 +1,54 @@
 | 
				
			|||||||
class Person { 
 | 
					class Person {
 | 
				
			||||||
  constructor(id, name, poster=undefined, birthday=undefined, deathday=undefined,
 | 
					  constructor(
 | 
				
			||||||
              adult=undefined, knownForDepartment=undefined) {
 | 
					    id,
 | 
				
			||||||
 | 
					    name,
 | 
				
			||||||
 | 
					    poster = undefined,
 | 
				
			||||||
 | 
					    birthday = undefined,
 | 
				
			||||||
 | 
					    deathday = undefined,
 | 
				
			||||||
 | 
					    adult = undefined,
 | 
				
			||||||
 | 
					    placeOfBirth = undefined,
 | 
				
			||||||
 | 
					    biography = undefined,
 | 
				
			||||||
 | 
					    knownForDepartment = undefined
 | 
				
			||||||
 | 
					  ) {
 | 
				
			||||||
    this.id = id;
 | 
					    this.id = id;
 | 
				
			||||||
    this.name = name;
 | 
					    this.name = name;
 | 
				
			||||||
    this.poster = poster;
 | 
					    this.poster = poster;
 | 
				
			||||||
    this.birthday = birthday;
 | 
					    this.birthday = birthday;
 | 
				
			||||||
    this.deathday = deathday;
 | 
					    this.deathday = deathday;
 | 
				
			||||||
    this.adult = adult;
 | 
					    this.adult = adult;
 | 
				
			||||||
 | 
					    this.placeOfBirth = placeOfBirth;
 | 
				
			||||||
 | 
					    this.biography = biography;
 | 
				
			||||||
    this.knownForDepartment = knownForDepartment;
 | 
					    this.knownForDepartment = knownForDepartment;
 | 
				
			||||||
    this.type = 'person';
 | 
					    this.type = "person";
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  static convertFromTmdbResponse(response) {
 | 
					  static convertFromTmdbResponse(response) {
 | 
				
			||||||
    const { id, name, poster, birthday, deathday, adult, known_for_department } = response;
 | 
					    const {
 | 
				
			||||||
 | 
					      id,
 | 
				
			||||||
 | 
					      name,
 | 
				
			||||||
 | 
					      profile_path,
 | 
				
			||||||
 | 
					      birthday,
 | 
				
			||||||
 | 
					      deathday,
 | 
				
			||||||
 | 
					      adult,
 | 
				
			||||||
 | 
					      place_of_birth,
 | 
				
			||||||
 | 
					      biography,
 | 
				
			||||||
 | 
					      known_for_department
 | 
				
			||||||
 | 
					    } = response;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const birthDay = new Date(birthday)
 | 
					    const birthDay = new Date(birthday);
 | 
				
			||||||
    const deathDay = deathday ? new Date(deathday) : null
 | 
					    const deathDay = deathday ? new Date(deathday) : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return new Person(id, name, poster, birthDay, deathDay, adult, known_for_department)
 | 
					    return new Person(
 | 
				
			||||||
 | 
					      id,
 | 
				
			||||||
 | 
					      name,
 | 
				
			||||||
 | 
					      profile_path,
 | 
				
			||||||
 | 
					      birthDay,
 | 
				
			||||||
 | 
					      deathDay,
 | 
				
			||||||
 | 
					      adult,
 | 
				
			||||||
 | 
					      place_of_birth,
 | 
				
			||||||
 | 
					      biography,
 | 
				
			||||||
 | 
					      known_for_department
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  createJsonResponse() {
 | 
					  createJsonResponse() {
 | 
				
			||||||
@@ -27,10 +58,12 @@ class Person {
 | 
				
			|||||||
      poster: this.poster,
 | 
					      poster: this.poster,
 | 
				
			||||||
      birthday: this.birthday,
 | 
					      birthday: this.birthday,
 | 
				
			||||||
      deathday: this.deathday,
 | 
					      deathday: this.deathday,
 | 
				
			||||||
 | 
					      place_of_birth: this.placeOfBirth,
 | 
				
			||||||
 | 
					      biography: this.biography,
 | 
				
			||||||
      known_for_department: this.knownForDepartment,
 | 
					      known_for_department: this.knownForDepartment,
 | 
				
			||||||
      adult: this.adult,
 | 
					      adult: this.adult,
 | 
				
			||||||
      type: this.type
 | 
					      type: this.type
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,26 +1,25 @@
 | 
				
			|||||||
const User = require('src/user/user');
 | 
					const User = require("src/user/user");
 | 
				
			||||||
const jwt = require('jsonwebtoken');
 | 
					const jwt = require("jsonwebtoken");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Token {
 | 
					class Token {
 | 
				
			||||||
  constructor(user, admin=false) {
 | 
					  constructor(user, admin = false, settings = null) {
 | 
				
			||||||
    this.user = user;
 | 
					    this.user = user;
 | 
				
			||||||
    this.admin = admin;
 | 
					    this.admin = admin;
 | 
				
			||||||
 | 
					    this.settings = settings;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
    * Generate a new token.
 | 
					   * Generate a new token.
 | 
				
			||||||
    * @param {String} secret a cipher of the token
 | 
					   * @param {String} secret a cipher of the token
 | 
				
			||||||
    * @returns {String}
 | 
					   * @returns {String}
 | 
				
			||||||
    */
 | 
					   */
 | 
				
			||||||
  toString(secret) {
 | 
					  toString(secret) {
 | 
				
			||||||
    const username = this.user.username;
 | 
					    const { user, admin, settings } = this;
 | 
				
			||||||
    const admin = this.admin;
 | 
					 | 
				
			||||||
    let data = { username }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (admin)
 | 
					    let data = { username: user.username, settings };
 | 
				
			||||||
      data = { ...data, admin }
 | 
					    if (admin) data["admin"] = admin;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return jwt.sign(data, secret, { expiresIn: '90d' });
 | 
					    return jwt.sign(data, secret, { expiresIn: "90d" });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
@@ -30,15 +29,12 @@ class Token {
 | 
				
			|||||||
   * @returns {Token}
 | 
					   * @returns {Token}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  static fromString(jwtToken, secret) {
 | 
					  static fromString(jwtToken, secret) {
 | 
				
			||||||
    let username = null;
 | 
					    const token = jwt.verify(jwtToken, secret, { clockTolerance: 10000 });
 | 
				
			||||||
 | 
					    if (token.username == null) throw new Error("Malformed token");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const token = jwt.verify(jwtToken, secret, { clockTolerance: 10000 })
 | 
					    const { username, admin, settings } = token;
 | 
				
			||||||
    if (token.username === undefined || token.username === null)
 | 
					    const user = new User(username);
 | 
				
			||||||
      throw new Error('Malformed token')
 | 
					    return new Token(user, admin, settings);
 | 
				
			||||||
 | 
					 | 
				
			||||||
    username = token.username
 | 
					 | 
				
			||||||
    const user = new User(username)
 | 
					 | 
				
			||||||
    return new Token(user)
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,219 +1,256 @@
 | 
				
			|||||||
const assert = require('assert');
 | 
					const assert = require("assert");
 | 
				
			||||||
const establishedDatabase = require('src/database/database');
 | 
					const establishedDatabase = require("src/database/database");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UserRepository {
 | 
					class UserRepository {
 | 
				
			||||||
  constructor(database) {
 | 
					  constructor(database) {
 | 
				
			||||||
    this.database = database || establishedDatabase;
 | 
					    this.database = database || establishedDatabase;
 | 
				
			||||||
    this.queries = {
 | 
					    this.queries = {
 | 
				
			||||||
      read: 'select * from user where lower(user_name) = lower(?)',
 | 
					      read: "select * from user where lower(user_name) = lower(?)",
 | 
				
			||||||
      create: 'insert into user (user_name) values (?)',
 | 
					      create: "insert into user (user_name) values (?)",
 | 
				
			||||||
      change: 'update user set password = ? where user_name = ?',
 | 
					      change: "update user set password = ? where user_name = ?",
 | 
				
			||||||
      retrieveHash: 'select * from user where user_name = ?',
 | 
					      retrieveHash: "select * from user where user_name = ?",
 | 
				
			||||||
      getAdminStateByUser: 'select admin from user where user_name = ?',
 | 
					      getAdminStateByUser: "select admin from user where user_name = ?",
 | 
				
			||||||
      link: 'update settings set plex_userid = ? where user_name = ?',
 | 
					      link: "update settings set plex_userid = ? where user_name = ?",
 | 
				
			||||||
      unlink: 'update settings set plex_userid = null where user_name = ?',
 | 
					      unlink: "update settings set plex_userid = null where user_name = ?",
 | 
				
			||||||
      createSettings: 'insert into settings (user_name) values (?)',
 | 
					      createSettings: "insert into settings (user_name) values (?)",
 | 
				
			||||||
      updateSettings: 'update settings set user_name = ?, dark_mode = ?, emoji = ?',
 | 
					      updateSettings:
 | 
				
			||||||
      getSettings: 'select * from settings where user_name = ?'
 | 
					        "update settings set user_name = ?, dark_mode = ?, emoji = ?",
 | 
				
			||||||
 | 
					      getSettings: "select * from settings where user_name = ?"
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
  * Create a user in a database.
 | 
					   * Create a user in a database.
 | 
				
			||||||
  * @param {User} user the user you want to create
 | 
					   * @param {User} user the user you want to create
 | 
				
			||||||
  * @returns {Promise}
 | 
					   * @returns {Promise}
 | 
				
			||||||
  */
 | 
					   */
 | 
				
			||||||
  create(user) {
 | 
					  create(user) {
 | 
				
			||||||
    return this.database.get(this.queries.read, user.username)
 | 
					    return this.database
 | 
				
			||||||
 | 
					      .get(this.queries.read, user.username)
 | 
				
			||||||
      .then(() => this.database.run(this.queries.create, user.username))
 | 
					      .then(() => this.database.run(this.queries.create, user.username))
 | 
				
			||||||
      .catch((error) => {
 | 
					      .catch(error => {
 | 
				
			||||||
        if (error.name === 'AssertionError' || error.message.endsWith('user_name')) {
 | 
					        if (
 | 
				
			||||||
           throw new Error('That username is already registered');
 | 
					          error.name === "AssertionError" ||
 | 
				
			||||||
 | 
					          error.message.endsWith("user_name")
 | 
				
			||||||
 | 
					        ) {
 | 
				
			||||||
 | 
					          throw new Error("That username is already registered");
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
        throw Error(error)
 | 
					        throw Error(error);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
  * Retrieve a password from a database.
 | 
					   * Retrieve a password from a database.
 | 
				
			||||||
  * @param {User} user the user you want to retrieve the password
 | 
					   * @param {User} user the user you want to retrieve the password
 | 
				
			||||||
  * @returns {Promise}
 | 
					   * @returns {Promise}
 | 
				
			||||||
  */
 | 
					   */
 | 
				
			||||||
  retrieveHash(user) {
 | 
					  retrieveHash(user) {
 | 
				
			||||||
    return this.database.get(this.queries.retrieveHash, user.username)
 | 
					    return this.database
 | 
				
			||||||
 | 
					      .get(this.queries.retrieveHash, user.username)
 | 
				
			||||||
      .then(row => {
 | 
					      .then(row => {
 | 
				
			||||||
        assert(row, 'The user does not exist.');
 | 
					        assert(row, "The user does not exist.");
 | 
				
			||||||
        return row.password;
 | 
					        return row.password;
 | 
				
			||||||
      })
 | 
					      })
 | 
				
			||||||
      .catch(err => { console.log(error); throw new Error('Unable to find your user.'); })
 | 
					      .catch(err => {
 | 
				
			||||||
 | 
					        console.log(error);
 | 
				
			||||||
 | 
					        throw new Error("Unable to find your user.");
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
  * Change a user's password in a database.
 | 
					   * Change a user's password in a database.
 | 
				
			||||||
  * @param {User} user the user you want to create
 | 
					   * @param {User} user the user you want to create
 | 
				
			||||||
  * @param {String} password the new password you want to change
 | 
					   * @param {String} password the new password you want to change
 | 
				
			||||||
  * @returns {Promise}
 | 
					   * @returns {Promise}
 | 
				
			||||||
  */
 | 
					   */
 | 
				
			||||||
  changePassword(user, password) {
 | 
					  changePassword(user, password) {
 | 
				
			||||||
    return this.database.run(this.queries.change, [password, user.username])
 | 
					    return this.database.run(this.queries.change, [password, user.username]);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
  * Link plex userid with seasoned user
 | 
					   * Link plex userid with seasoned user
 | 
				
			||||||
  * @param {String} username the user you want to lunk plex userid with
 | 
					   * @param {String} username the user you want to lunk plex userid with
 | 
				
			||||||
  * @param {Number} plexUserID plex unique id
 | 
					   * @param {Number} plexUserID plex unique id
 | 
				
			||||||
  * @returns {Promsie}
 | 
					   * @returns {Promsie}
 | 
				
			||||||
  */
 | 
					   */
 | 
				
			||||||
  linkPlexUserId(username, plexUserID) {
 | 
					  linkPlexUserId(username, plexUserID) {
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
      this.database.run(this.queries.link, [plexUserID, username])
 | 
					      this.database
 | 
				
			||||||
 | 
					        .run(this.queries.link, [plexUserID, username])
 | 
				
			||||||
        .then(row => resolve(row))
 | 
					        .then(row => resolve(row))
 | 
				
			||||||
        .catch(error => {
 | 
					        .catch(error => {
 | 
				
			||||||
          // TODO log this unknown db error
 | 
					          // TODO log this unknown db error
 | 
				
			||||||
          console.log('db error', error)
 | 
					          console.error("db error", error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          reject({
 | 
					          reject({
 | 
				
			||||||
            status: 500,
 | 
					            status: 500,
 | 
				
			||||||
            message: 'An unexpected error occured while linking plex and seasoned accounts',
 | 
					            message:
 | 
				
			||||||
            source: 'seasoned database'
 | 
					              "An unexpected error occured while linking plex and seasoned accounts",
 | 
				
			||||||
          })
 | 
					            source: "seasoned database"
 | 
				
			||||||
        })
 | 
					          });
 | 
				
			||||||
    })
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
  * Unlink plex userid with seasoned user
 | 
					   * Unlink plex userid with seasoned user
 | 
				
			||||||
  * @param {User} user the user you want to lunk plex userid with
 | 
					   * @param {User} user the user you want to lunk plex userid with
 | 
				
			||||||
  * @returns {Promsie}
 | 
					   * @returns {Promsie}
 | 
				
			||||||
  */
 | 
					   */
 | 
				
			||||||
  unlinkPlexUserId(username) {
 | 
					  unlinkPlexUserId(username) {
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
      this.database.run(this.queries.unlink, username)
 | 
					      this.database
 | 
				
			||||||
 | 
					        .run(this.queries.unlink, username)
 | 
				
			||||||
        .then(row => resolve(row))
 | 
					        .then(row => resolve(row))
 | 
				
			||||||
        .catch(error => {
 | 
					        .catch(error => {
 | 
				
			||||||
          // TODO log this unknown db error
 | 
					          // TODO log this unknown db error
 | 
				
			||||||
          console.log('db error', error)
 | 
					          console.log("db error", error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          reject({
 | 
					          reject({
 | 
				
			||||||
            status: 500,
 | 
					            status: 500,
 | 
				
			||||||
            message: 'An unexpected error occured while unlinking plex and seasoned accounts',
 | 
					            message:
 | 
				
			||||||
            source: 'seasoned database'
 | 
					              "An unexpected error occured while unlinking plex and seasoned accounts",
 | 
				
			||||||
          })
 | 
					            source: "seasoned database"
 | 
				
			||||||
        })
 | 
					          });
 | 
				
			||||||
    })
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 /**
 | 
					  /**
 | 
				
			||||||
  * Check if the user has boolean flag set for admin in database
 | 
					   * Check if the user has boolean flag set for admin in database
 | 
				
			||||||
  * @param {User} user object
 | 
					   * @param {User} user object
 | 
				
			||||||
  * @returns {Promsie}
 | 
					   * @returns {Promsie}
 | 
				
			||||||
  */
 | 
					   */
 | 
				
			||||||
  checkAdmin(user) {
 | 
					  checkAdmin(user) {
 | 
				
			||||||
    return this.database.get(this.queries.getAdminStateByUser, user.username)
 | 
					    return this.database
 | 
				
			||||||
      .then((row) => row.admin);
 | 
					      .get(this.queries.getAdminStateByUser, user.username)
 | 
				
			||||||
 | 
					      .then(row => row.admin);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 /**
 | 
					  /**
 | 
				
			||||||
  * Get settings for user matching string username
 | 
					   * Get settings for user matching string username
 | 
				
			||||||
  * @param {String} username
 | 
					   * @param {String} username
 | 
				
			||||||
  * @returns {Promsie}
 | 
					   * @returns {Promsie}
 | 
				
			||||||
  */
 | 
					   */
 | 
				
			||||||
  getSettings(username) {
 | 
					  getSettings(username) {
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
      this.database.get(this.queries.getSettings, username)
 | 
					      this.database
 | 
				
			||||||
        .then(async (row) => {
 | 
					        .get(this.queries.getSettings, username)
 | 
				
			||||||
 | 
					        .then(async row => {
 | 
				
			||||||
          if (row == null) {
 | 
					          if (row == null) {
 | 
				
			||||||
            console.log(`settings do not exist for user: ${username}. Creating settings entry.`)
 | 
					            console.debug(
 | 
				
			||||||
 | 
					              `settings do not exist for user: ${username}. Creating settings entry.`
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            const userExistsWithUsername = await this.database.get('select * from user where user_name is ?', username)
 | 
					            const userExistsWithUsername = await this.database.get(
 | 
				
			||||||
 | 
					              "select * from user where user_name is ?",
 | 
				
			||||||
 | 
					              username
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
            if (userExistsWithUsername !== undefined) {
 | 
					            if (userExistsWithUsername !== undefined) {
 | 
				
			||||||
              try {
 | 
					              try {
 | 
				
			||||||
                resolve(this.dbCreateSettings(username))
 | 
					                resolve(this.dbCreateSettings(username));
 | 
				
			||||||
              } catch (error) {
 | 
					              } catch (error) {
 | 
				
			||||||
                reject(error)
 | 
					                reject(error);
 | 
				
			||||||
              }
 | 
					              }
 | 
				
			||||||
            } else {
 | 
					            } else {
 | 
				
			||||||
              reject({
 | 
					              reject({
 | 
				
			||||||
                status: 404,
 | 
					                status: 404,
 | 
				
			||||||
                message: 'User not found, no settings to get'
 | 
					                message: "User not found, no settings to get"
 | 
				
			||||||
              })
 | 
					              });
 | 
				
			||||||
            }
 | 
					            }
 | 
				
			||||||
          }
 | 
					          }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          resolve(row)
 | 
					          resolve(row);
 | 
				
			||||||
        })
 | 
					        })
 | 
				
			||||||
        .catch(error => {
 | 
					        .catch(error => {
 | 
				
			||||||
          console.error('Unexpected error occured while fetching settings for your account. Error:', error)
 | 
					          console.error(
 | 
				
			||||||
 | 
					            "Unexpected error occured while fetching settings for your account. Error:",
 | 
				
			||||||
 | 
					            error
 | 
				
			||||||
 | 
					          );
 | 
				
			||||||
          reject({
 | 
					          reject({
 | 
				
			||||||
            status: 500,
 | 
					            status: 500,
 | 
				
			||||||
            message: 'An unexpected error occured while fetching settings for your account',
 | 
					            message:
 | 
				
			||||||
            source: 'seasoned database'
 | 
					              "An unexpected error occured while fetching settings for your account",
 | 
				
			||||||
          })
 | 
					            source: "seasoned database"
 | 
				
			||||||
        })
 | 
					          });
 | 
				
			||||||
    })
 | 
					        });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 /**
 | 
					  /**
 | 
				
			||||||
  * Update settings values for user matching string username
 | 
					   * Update settings values for user matching string username
 | 
				
			||||||
  * @param {String} username
 | 
					   * @param {String} username
 | 
				
			||||||
  * @param {String} dark_mode
 | 
					   * @param {String} dark_mode
 | 
				
			||||||
  * @param {String} emoji
 | 
					   * @param {String} emoji
 | 
				
			||||||
  * @returns {Promsie}
 | 
					   * @returns {Promsie}
 | 
				
			||||||
  */
 | 
					   */
 | 
				
			||||||
  updateSettings(username, dark_mode=undefined, emoji=undefined) {
 | 
					  updateSettings(username, dark_mode = undefined, emoji = undefined) {
 | 
				
			||||||
    const settings = this.getSettings(username)
 | 
					    const settings = this.getSettings(username);
 | 
				
			||||||
    dark_mode = dark_mode !== undefined ? dark_mode : settings.dark_mode
 | 
					    dark_mode = dark_mode !== undefined ? dark_mode : settings.dark_mode;
 | 
				
			||||||
    emoji = emoji !== undefined ? emoji : settings.emoji
 | 
					    emoji = emoji !== undefined ? emoji : settings.emoji;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return this.dbUpdateSettings(username, dark_mode, emoji)
 | 
					    return this.dbUpdateSettings(username, dark_mode, emoji).catch(error => {
 | 
				
			||||||
      .catch(error => {
 | 
					      if (error.status && error.message) {
 | 
				
			||||||
        if (error.status && error.message) {
 | 
					        return error;
 | 
				
			||||||
          return error
 | 
					      }
 | 
				
			||||||
        }
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return {
 | 
					      return {
 | 
				
			||||||
          status: 500,
 | 
					        status: 500,
 | 
				
			||||||
          message: 'An unexpected error occured while updating settings for your account'
 | 
					        message:
 | 
				
			||||||
        }
 | 
					          "An unexpected error occured while updating settings for your account"
 | 
				
			||||||
      })
 | 
					      };
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 /**
 | 
					  /**
 | 
				
			||||||
  * Helper function for creating settings in the database
 | 
					   * Helper function for creating settings in the database
 | 
				
			||||||
  * @param {String} username
 | 
					   * @param {String} username
 | 
				
			||||||
  * @returns {Promsie}
 | 
					   * @returns {Promsie}
 | 
				
			||||||
  */
 | 
					   */
 | 
				
			||||||
  dbCreateSettings(username) {
 | 
					  dbCreateSettings(username) {
 | 
				
			||||||
    return this.database.run(this.queries.createSettings, username)
 | 
					    return this.database
 | 
				
			||||||
 | 
					      .run(this.queries.createSettings, username)
 | 
				
			||||||
      .then(() => this.database.get(this.queries.getSettings, username))
 | 
					      .then(() => this.database.get(this.queries.getSettings, username))
 | 
				
			||||||
      .catch(error => rejectUnexpectedDatabaseError('Unexpected error occured while creating settings', 503, error))
 | 
					      .catch(error =>
 | 
				
			||||||
 | 
					        rejectUnexpectedDatabaseError(
 | 
				
			||||||
 | 
					          "Unexpected error occured while creating settings",
 | 
				
			||||||
 | 
					          503,
 | 
				
			||||||
 | 
					          error
 | 
				
			||||||
 | 
					        )
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 /**
 | 
					  /**
 | 
				
			||||||
  * Helper function for updating settings in the database
 | 
					   * Helper function for updating settings in the database
 | 
				
			||||||
  * @param {String} username
 | 
					   * @param {String} username
 | 
				
			||||||
  * @returns {Promsie}
 | 
					   * @returns {Promsie}
 | 
				
			||||||
  */
 | 
					   */
 | 
				
			||||||
  dbUpdateSettings(username, dark_mode, emoji) {
 | 
					  dbUpdateSettings(username, dark_mode, emoji) {
 | 
				
			||||||
    return new Promise((resolve, reject) =>
 | 
					    return new Promise((resolve, reject) =>
 | 
				
			||||||
      this.database.run(this.queries.updateSettings, [username, dark_mode, emoji])
 | 
					      this.database
 | 
				
			||||||
      .then(row => resolve(row)))
 | 
					        .run(this.queries.updateSettings, [username, dark_mode, emoji])
 | 
				
			||||||
 | 
					        .then(row => resolve(row))
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rejectUnexpectedDatabaseError = (
 | 
				
			||||||
const rejectUnexpectedDatabaseError = (message, status, error, reject=null) => {
 | 
					  message,
 | 
				
			||||||
  console.error(error)
 | 
					  status,
 | 
				
			||||||
 | 
					  error,
 | 
				
			||||||
 | 
					  reject = null
 | 
				
			||||||
 | 
					) => {
 | 
				
			||||||
 | 
					  console.error(error);
 | 
				
			||||||
  const body = {
 | 
					  const body = {
 | 
				
			||||||
    status,
 | 
					    status,
 | 
				
			||||||
    message,
 | 
					    message,
 | 
				
			||||||
    source: 'seasoned database'
 | 
					    source: "seasoned database"
 | 
				
			||||||
  }
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (reject == null) {
 | 
					  if (reject == null) {
 | 
				
			||||||
    return new Promise((resolve, reject) => reject(body))
 | 
					    return new Promise((resolve, reject) => reject(body));
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  reject(body)
 | 
					  reject(body);
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = UserRepository;
 | 
					module.exports = UserRepository;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,10 +1,10 @@
 | 
				
			|||||||
const bcrypt = require('bcrypt');
 | 
					const bcrypt = require("bcrypt");
 | 
				
			||||||
const UserRepository = require('src/user/userRepository');
 | 
					const UserRepository = require("src/user/userRepository");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UserSecurity {
 | 
					class UserSecurity {
 | 
				
			||||||
  constructor(database) {
 | 
					  constructor(database) {
 | 
				
			||||||
  this.userRepository = new UserRepository(database);
 | 
					    this.userRepository = new UserRepository(database);
 | 
				
			||||||
}
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  /**
 | 
					  /**
 | 
				
			||||||
   * Create a new user in PlanFlix.
 | 
					   * Create a new user in PlanFlix.
 | 
				
			||||||
@@ -13,15 +13,15 @@ class UserSecurity {
 | 
				
			|||||||
   * @returns {Promise}
 | 
					   * @returns {Promise}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  createNewUser(user, clearPassword) {
 | 
					  createNewUser(user, clearPassword) {
 | 
				
			||||||
    if (user.username.trim() === '') {
 | 
					    if (user.username.trim() === "") {
 | 
				
			||||||
      throw new Error('The username is empty.');
 | 
					      throw new Error("The username is empty.");
 | 
				
			||||||
    } else if (clearPassword.trim() === '') {
 | 
					    } else if (clearPassword.trim() === "") {
 | 
				
			||||||
      throw new Error('The password is empty.');
 | 
					      throw new Error("The password is empty.");
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      return Promise.resolve()
 | 
					      return this.userRepository
 | 
				
			||||||
        .then(() => this.userRepository.create(user))
 | 
					        .create(user)
 | 
				
			||||||
        .then(() => UserSecurity.hashPassword(clearPassword))
 | 
					        .then(() => UserSecurity.hashPassword(clearPassword))
 | 
				
			||||||
        .then(hash => this.userRepository.changePassword(user, hash))
 | 
					        .then(hash => this.userRepository.changePassword(user, hash));
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -32,24 +32,25 @@ class UserSecurity {
 | 
				
			|||||||
   * @returns {Promise}
 | 
					   * @returns {Promise}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  login(user, clearPassword) {
 | 
					  login(user, clearPassword) {
 | 
				
			||||||
    return Promise.resolve()
 | 
					    return this.userRepository
 | 
				
			||||||
      .then(() => this.userRepository.retrieveHash(user))
 | 
					      .retrieveHash(user)
 | 
				
			||||||
      .then(hash => UserSecurity.compareHashes(hash, clearPassword))
 | 
					      .then(hash => UserSecurity.compareHashes(hash, clearPassword))
 | 
				
			||||||
      .catch(() => { throw new Error('Incorrect username or password.'); });
 | 
					      .catch(() => {
 | 
				
			||||||
 | 
					        throw new Error("Incorrect username or password.");
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   /**
 | 
					  /**
 | 
				
			||||||
    * Compare between a password and a hash password from database.
 | 
					   * Compare between a password and a hash password from database.
 | 
				
			||||||
    * @param {String} hash the hash password from database
 | 
					   * @param {String} hash the hash password from database
 | 
				
			||||||
    * @param {String} clearPassword the user's password
 | 
					   * @param {String} clearPassword the user's password
 | 
				
			||||||
    * @returns {Promise}
 | 
					   * @returns {Promise}
 | 
				
			||||||
    */
 | 
					   */
 | 
				
			||||||
  static compareHashes(hash, clearPassword) {
 | 
					  static compareHashes(hash, clearPassword) {
 | 
				
			||||||
    return new Promise((resolve, reject) => {
 | 
					    return new Promise((resolve, reject) => {
 | 
				
			||||||
      bcrypt.compare(clearPassword, hash, (error, match) => {
 | 
					      bcrypt.compare(clearPassword, hash, (error, match) => {
 | 
				
			||||||
        if (match)
 | 
					        if (match) resolve(true);
 | 
				
			||||||
          resolve()
 | 
					        reject(false);
 | 
				
			||||||
        reject()
 | 
					 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
@@ -60,9 +61,11 @@ class UserSecurity {
 | 
				
			|||||||
   * @returns {Promise}
 | 
					   * @returns {Promise}
 | 
				
			||||||
   */
 | 
					   */
 | 
				
			||||||
  static hashPassword(clearPassword) {
 | 
					  static hashPassword(clearPassword) {
 | 
				
			||||||
    return new Promise((resolve) => {
 | 
					    return new Promise(resolve => {
 | 
				
			||||||
      const saltRounds = 10;
 | 
					      const saltRounds = 10;
 | 
				
			||||||
      bcrypt.hash(clearPassword, saltRounds, (error, hash) => {
 | 
					      bcrypt.hash(clearPassword, saltRounds, (error, hash) => {
 | 
				
			||||||
 | 
					        if (error) reject(error);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        resolve(hash);
 | 
					        resolve(hash);
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,146 +1,239 @@
 | 
				
			|||||||
const express = require('express');
 | 
					const express = require("express");
 | 
				
			||||||
const Raven = require('raven');
 | 
					const Raven = require("raven");
 | 
				
			||||||
const bodyParser = require('body-parser');
 | 
					const cookieParser = require("cookie-parser");
 | 
				
			||||||
const tokenToUser = require('./middleware/tokenToUser');
 | 
					const bodyParser = require("body-parser");
 | 
				
			||||||
const mustBeAuthenticated = require('./middleware/mustBeAuthenticated');
 | 
					 | 
				
			||||||
const mustBeAdmin = require('./middleware/mustBeAdmin');
 | 
					 | 
				
			||||||
const mustHaveAccountLinkedToPlex = require('./middleware/mustHaveAccountLinkedToPlex');
 | 
					 | 
				
			||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const listController = require('./controllers/list/listController');
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
const tautulli = require('./controllers/user/viewHistory.js');
 | 
					
 | 
				
			||||||
const SettingsController = require('./controllers/user/settings');
 | 
					const reqTokenToUser = require("./middleware/reqTokenToUser");
 | 
				
			||||||
const AuthenticatePlexAccountController = require('./controllers/user/AuthenticatePlexAccount');
 | 
					const mustBeAuthenticated = require("./middleware/mustBeAuthenticated");
 | 
				
			||||||
 | 
					const mustBeAdmin = require("./middleware/mustBeAdmin");
 | 
				
			||||||
 | 
					const mustHaveAccountLinkedToPlex = require("./middleware/mustHaveAccountLinkedToPlex");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const listController = require("./controllers/list/listController");
 | 
				
			||||||
 | 
					const tautulli = require("./controllers/user/viewHistory.js");
 | 
				
			||||||
 | 
					const SettingsController = require("./controllers/user/settings");
 | 
				
			||||||
 | 
					const AuthenticatePlexAccountController = require("./controllers/user/authenticatePlexAccount");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: Have our raven router check if there is a value, if not don't enable raven.
 | 
					// TODO: Have our raven router check if there is a value, if not don't enable raven.
 | 
				
			||||||
Raven.config(configuration.get('raven', 'DSN')).install();
 | 
					Raven.config(configuration.get("raven", "DSN")).install();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const app = express(); // define our app using express
 | 
					const app = express(); // define our app using express
 | 
				
			||||||
app.use(Raven.requestHandler());
 | 
					app.use(Raven.requestHandler());
 | 
				
			||||||
app.use(bodyParser.json());
 | 
					app.use(bodyParser.json());
 | 
				
			||||||
 | 
					app.use(cookieParser());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const router = express.Router();
 | 
					const router = express.Router();
 | 
				
			||||||
const allowedOrigins = ['https://kevinmidboe.com', 'http://localhost:8080'];
 | 
					const allowedOrigins = configuration.get("webserver", "origins");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: All JSON handling in a single router
 | 
					// TODO: All JSON handling in a single router
 | 
				
			||||||
// router.use(bodyParser.json());
 | 
					// router.use(bodyParser.json());
 | 
				
			||||||
app.use(bodyParser.urlencoded({ extended: true }));
 | 
					app.use(bodyParser.urlencoded({ extended: true }));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/* Decode the Authorization header if provided */
 | 
					/* Check header and cookie for authentication and set req.loggedInUser */
 | 
				
			||||||
router.use(tokenToUser);
 | 
					router.use(reqTokenToUser);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO: Should have a separate middleware/router for handling headers.
 | 
					// TODO: Should have a separate middleware/router for handling headers.
 | 
				
			||||||
router.use((req, res, next) => {
 | 
					router.use((req, res, next) => {
 | 
				
			||||||
   // TODO add logging of all incoming
 | 
					  // TODO add logging of all incoming
 | 
				
			||||||
  const origin = req.headers.origin;
 | 
					  // const origin = req.headers.origin;
 | 
				
			||||||
   if (allowedOrigins.indexOf(origin) > -1) {
 | 
					  // if (allowedOrigins.indexOf(origin) > -1) {
 | 
				
			||||||
       res.setHeader('Access-Control-Allow-Origin', origin);
 | 
					  //   res.setHeader("Access-Control-Allow-Origin", origin);
 | 
				
			||||||
   }
 | 
					  // }
 | 
				
			||||||
   res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization, loggedinuser');
 | 
					 | 
				
			||||||
   res.header('Access-Control-Allow-Methods', 'POST, GET, PUT');
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
   next();
 | 
					  res.header(
 | 
				
			||||||
 | 
					    "Access-Control-Allow-Headers",
 | 
				
			||||||
 | 
					    "Content-Type, Authorization, loggedinuser, set-cookie"
 | 
				
			||||||
 | 
					  );
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  res.header("Access-Control-Allow-Credentials", "true");
 | 
				
			||||||
 | 
					  res.header("Access-Control-Allow-Methods", "POST, GET, PUT, OPTIONS");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  next();
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.get('/', function mainHandler(req, res) {
 | 
					router.get("/", (req, res) => {
 | 
				
			||||||
   throw new Error('Broke!');
 | 
					  res.send("welcome to seasoned api");
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
app.use(Raven.errorHandler());
 | 
					app.use(Raven.errorHandler());
 | 
				
			||||||
app.use(function onError(err, req, res, next) {
 | 
					app.use((err, req, res, next) => {
 | 
				
			||||||
   res.statusCode = 500;
 | 
					  res.statusCode = 500;
 | 
				
			||||||
   res.end(res.sentry + '\n');
 | 
					  res.end(res.sentry + "\n");
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * User
 | 
					 * User
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
router.post('/v1/user', require('./controllers/user/register.js'));
 | 
					router.post("/v1/user", require("./controllers/user/register.js"));
 | 
				
			||||||
router.post('/v1/user/login', require('./controllers/user/login.js'));
 | 
					router.post("/v1/user/login", require("./controllers/user/login.js"));
 | 
				
			||||||
router.get('/v1/user/settings', mustBeAuthenticated, SettingsController.getSettingsController);
 | 
					router.post("/v1/user/logout", require("./controllers/user/logout.js"));
 | 
				
			||||||
router.put('/v1/user/settings', mustBeAuthenticated, SettingsController.updateSettingsController);
 | 
					router.get(
 | 
				
			||||||
router.get('/v1/user/search_history', mustBeAuthenticated, require('./controllers/user/searchHistory.js'));
 | 
					  "/v1/user/settings",
 | 
				
			||||||
router.get('/v1/user/requests', mustBeAuthenticated, require('./controllers/user/requests.js'));
 | 
					  mustBeAuthenticated,
 | 
				
			||||||
router.post('/v1/user/link_plex', mustBeAuthenticated, AuthenticatePlexAccountController.link);
 | 
					  SettingsController.getSettingsController
 | 
				
			||||||
router.post('/v1/user/unlink_plex', mustBeAuthenticated, AuthenticatePlexAccountController.unlink);
 | 
					);
 | 
				
			||||||
 | 
					router.put(
 | 
				
			||||||
 | 
					  "/v1/user/settings",
 | 
				
			||||||
 | 
					  mustBeAuthenticated,
 | 
				
			||||||
 | 
					  SettingsController.updateSettingsController
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					router.get(
 | 
				
			||||||
 | 
					  "/v1/user/search_history",
 | 
				
			||||||
 | 
					  mustBeAuthenticated,
 | 
				
			||||||
 | 
					  require("./controllers/user/searchHistory.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					router.get(
 | 
				
			||||||
 | 
					  "/v1/user/requests",
 | 
				
			||||||
 | 
					  mustBeAuthenticated,
 | 
				
			||||||
 | 
					  require("./controllers/user/requests.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					router.post(
 | 
				
			||||||
 | 
					  "/v1/user/link_plex",
 | 
				
			||||||
 | 
					  mustBeAuthenticated,
 | 
				
			||||||
 | 
					  AuthenticatePlexAccountController.link
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					router.post(
 | 
				
			||||||
 | 
					  "/v1/user/unlink_plex",
 | 
				
			||||||
 | 
					  mustBeAuthenticated,
 | 
				
			||||||
 | 
					  AuthenticatePlexAccountController.unlink
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.get('/v1/user/view_history', mustHaveAccountLinkedToPlex, tautulli.userViewHistoryController);
 | 
					router.get(
 | 
				
			||||||
router.get('/v1/user/watch_time', mustHaveAccountLinkedToPlex, tautulli.watchTimeStatsController);
 | 
					  "/v1/user/view_history",
 | 
				
			||||||
router.get('/v1/user/plays_by_day', mustHaveAccountLinkedToPlex, tautulli.getPlaysByDaysController);
 | 
					  mustHaveAccountLinkedToPlex,
 | 
				
			||||||
router.get('/v1/user/plays_by_dayofweek', mustHaveAccountLinkedToPlex, tautulli.getPlaysByDayOfWeekController);
 | 
					  tautulli.userViewHistoryController
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					router.get(
 | 
				
			||||||
 | 
					  "/v1/user/watch_time",
 | 
				
			||||||
 | 
					  mustHaveAccountLinkedToPlex,
 | 
				
			||||||
 | 
					  tautulli.watchTimeStatsController
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					router.get(
 | 
				
			||||||
 | 
					  "/v1/user/plays_by_day",
 | 
				
			||||||
 | 
					  mustHaveAccountLinkedToPlex,
 | 
				
			||||||
 | 
					  tautulli.getPlaysByDaysController
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					router.get(
 | 
				
			||||||
 | 
					  "/v1/user/plays_by_dayofweek",
 | 
				
			||||||
 | 
					  mustHaveAccountLinkedToPlex,
 | 
				
			||||||
 | 
					  tautulli.getPlaysByDayOfWeekController
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Seasoned
 | 
					 * Seasoned
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
router.get('/v1/seasoned/all', require('./controllers/seasoned/readStrays.js'));
 | 
					router.get("/v1/seasoned/all", require("./controllers/seasoned/readStrays.js"));
 | 
				
			||||||
router.get('/v1/seasoned/:strayId', require('./controllers/seasoned/strayById.js'));
 | 
					router.get(
 | 
				
			||||||
router.post('/v1/seasoned/verify/:strayId', require('./controllers/seasoned/verifyStray.js'));
 | 
					  "/v1/seasoned/:strayId",
 | 
				
			||||||
 | 
					  require("./controllers/seasoned/strayById.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					router.post(
 | 
				
			||||||
 | 
					  "/v1/seasoned/verify/:strayId",
 | 
				
			||||||
 | 
					  require("./controllers/seasoned/verifyStray.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.get('/v2/search/', require('./controllers/search/multiSearch.js'));
 | 
					router.get("/v2/search/", require("./controllers/search/multiSearch.js"));
 | 
				
			||||||
router.get('/v2/search/movie', require('./controllers/search/movieSearch.js'));
 | 
					router.get("/v2/search/movie", require("./controllers/search/movieSearch.js"));
 | 
				
			||||||
router.get('/v2/search/show', require('./controllers/search/showSearch.js'));
 | 
					router.get("/v2/search/show", require("./controllers/search/showSearch.js"));
 | 
				
			||||||
router.get('/v2/search/person', require('./controllers/search/personSearch.js'));
 | 
					router.get(
 | 
				
			||||||
 | 
					  "/v2/search/person",
 | 
				
			||||||
 | 
					  require("./controllers/search/personSearch.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.get('/v2/movie/now_playing', listController.nowPlayingMovies);
 | 
					router.get("/v2/movie/now_playing", listController.nowPlayingMovies);
 | 
				
			||||||
router.get('/v2/movie/popular', listController.popularMovies);
 | 
					router.get("/v2/movie/popular", listController.popularMovies);
 | 
				
			||||||
router.get('/v2/movie/top_rated', listController.topRatedMovies);
 | 
					router.get("/v2/movie/top_rated", listController.topRatedMovies);
 | 
				
			||||||
router.get('/v2/movie/upcoming', listController.upcomingMovies);
 | 
					router.get("/v2/movie/upcoming", listController.upcomingMovies);
 | 
				
			||||||
 | 
					router.get("/v2/movie/:id/credits", require("./controllers/movie/credits.js"));
 | 
				
			||||||
 | 
					router.get(
 | 
				
			||||||
 | 
					  "/v2/movie/:id/release_dates",
 | 
				
			||||||
 | 
					  require("./controllers/movie/releaseDates.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					router.get("/v2/movie/:id", require("./controllers/movie/info.js"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.get('/v2/show/now_playing', listController.nowPlayingShows);
 | 
					router.get("/v2/show/now_playing", listController.nowPlayingShows);
 | 
				
			||||||
router.get('/v2/show/popular', listController.popularShows);
 | 
					router.get("/v2/show/popular", listController.popularShows);
 | 
				
			||||||
router.get('/v2/show/top_rated', listController.topRatedShows);
 | 
					router.get("/v2/show/top_rated", listController.topRatedShows);
 | 
				
			||||||
 | 
					router.get("/v2/show/:id/credits", require("./controllers/show/credits.js"));
 | 
				
			||||||
 | 
					router.get("/v2/show/:id", require("./controllers/show/info.js"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.get('/v2/movie/:id/credits', require('./controllers/movie/credits.js'));
 | 
					router.get(
 | 
				
			||||||
router.get('/v2/movie/:id/release_dates', require('./controllers/movie/releaseDates.js'));
 | 
					  "/v2/person/:id/credits",
 | 
				
			||||||
router.get('/v2/show/:id/credits', require('./controllers/show/credits.js'));
 | 
					  require("./controllers/person/credits.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
router.get('/v2/movie/:id', require('./controllers/movie/info.js'));
 | 
					router.get("/v2/person/:id", require("./controllers/person/info.js"));
 | 
				
			||||||
router.get('/v2/show/:id', require('./controllers/show/info.js'));
 | 
					 | 
				
			||||||
router.get('/v2/person/:id', require('./controllers/person/info.js'));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Plex
 | 
					 * Plex
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
router.get('/v2/plex/search', require('./controllers/plex/search'));
 | 
					router.get("/v2/plex/search", require("./controllers/plex/search"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * List
 | 
					 * List
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
router.get('/v1/plex/search', require('./controllers/plex/searchMedia.js'));
 | 
					router.get("/v1/plex/search", require("./controllers/plex/searchMedia.js"));
 | 
				
			||||||
router.get('/v1/plex/playing', require('./controllers/plex/plexPlaying.js'));
 | 
					router.get("/v1/plex/playing", require("./controllers/plex/plexPlaying.js"));
 | 
				
			||||||
router.get('/v1/plex/request', require('./controllers/plex/searchRequest.js'));
 | 
					router.get("/v1/plex/request", require("./controllers/plex/searchRequest.js"));
 | 
				
			||||||
router.get('/v1/plex/request/:mediaId', require('./controllers/plex/readRequest.js'));
 | 
					router.get(
 | 
				
			||||||
router.post('/v1/plex/request/:mediaId', require('./controllers/plex/submitRequest.js'));
 | 
					  "/v1/plex/request/:mediaId",
 | 
				
			||||||
router.post('/v1/plex/hook', require('./controllers/plex/hookDump.js'));
 | 
					  require("./controllers/plex/readRequest.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					router.post(
 | 
				
			||||||
 | 
					  "/v1/plex/request/:mediaId",
 | 
				
			||||||
 | 
					  require("./controllers/plex/submitRequest.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					router.post("/v1/plex/hook", require("./controllers/plex/hookDump.js"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					router.get(
 | 
				
			||||||
 | 
					  "/v1/plex/watch-link",
 | 
				
			||||||
 | 
					  mustBeAuthenticated,
 | 
				
			||||||
 | 
					  require("./controllers/plex/watchDirectLink.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Requests
 | 
					 * Requests
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
router.get('/v2/request', require('./controllers/request/fetchAllRequests.js'));
 | 
					router.get("/v2/request", require("./controllers/request/fetchAllRequests.js"));
 | 
				
			||||||
router.get('/v2/request/:id', require('./controllers/request/getRequest.js'));
 | 
					router.get("/v2/request/:id", require("./controllers/request/getRequest.js"));
 | 
				
			||||||
router.post('/v2/request', require('./controllers/request/requestTmdbId.js'));
 | 
					router.post("/v2/request", require("./controllers/request/requestTmdbId.js"));
 | 
				
			||||||
router.get('/v1/plex/requests/all', require('./controllers/plex/fetchRequested.js'));
 | 
					router.get(
 | 
				
			||||||
router.put('/v1/plex/request/:requestId', mustBeAuthenticated, require('./controllers/plex/updateRequested.js'));
 | 
					  "/v1/plex/requests/all",
 | 
				
			||||||
 | 
					  require("./controllers/plex/fetchRequested.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					router.put(
 | 
				
			||||||
 | 
					  "/v1/plex/request/:requestId",
 | 
				
			||||||
 | 
					  mustBeAuthenticated,
 | 
				
			||||||
 | 
					  require("./controllers/plex/updateRequested.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Pirate
 | 
					 * Pirate
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
router.get('/v1/pirate/search', mustBeAuthenticated, require('./controllers/pirate/searchTheBay.js'));
 | 
					router.get(
 | 
				
			||||||
router.post('/v1/pirate/add', mustBeAuthenticated, require('./controllers/pirate/addMagnet.js'));
 | 
					  "/v1/pirate/search",
 | 
				
			||||||
 | 
					  mustBeAuthenticated,
 | 
				
			||||||
 | 
					  require("./controllers/pirate/searchTheBay.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					router.post(
 | 
				
			||||||
 | 
					  "/v1/pirate/add",
 | 
				
			||||||
 | 
					  mustBeAuthenticated,
 | 
				
			||||||
 | 
					  require("./controllers/pirate/addMagnet.js")
 | 
				
			||||||
 | 
					);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * git
 | 
					 * git
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
router.post('/v1/git/dump', require('./controllers/git/dumpHook.js'));
 | 
					router.post("/v1/git/dump", require("./controllers/git/dumpHook.js"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * misc
 | 
					 * misc
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 router.get('/v1/emoji', require('./controllers/misc/emoji.js'));
 | 
					router.get("/v1/emoji", require("./controllers/misc/emoji.js"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// REGISTER OUR ROUTES -------------------------------
 | 
					// REGISTER OUR ROUTES -------------------------------
 | 
				
			||||||
// all of our routes will be prefixed with /api
 | 
					// all of our routes will be prefixed with /api
 | 
				
			||||||
app.use('/api', router);
 | 
					app.use("/api", router);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = app;
 | 
					module.exports = app;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,6 @@
 | 
				
			|||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require('src/config/configuration').getInstance();
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const TMDB = require('src/tmdb/tmdb');
 | 
				
			||||||
const cache = new Cache();
 | 
					const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
// there should be a translate function from query params to 
 | 
					// there should be a translate function from query params to 
 | 
				
			||||||
// tmdb list that is valid. Should it be a helper function or does it 
 | 
					// tmdb list that is valid. Should it be a helper function or does it 
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,7 @@
 | 
				
			|||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require('src/config/configuration').getInstance();
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const TMDB = require('src/tmdb/tmdb');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cache = new Cache();
 | 
					const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const movieCreditsController = (req, res) => {
 | 
					const movieCreditsController = (req, res) => {
 | 
				
			||||||
  const movieId = req.params.id;
 | 
					  const movieId = req.params.id;
 | 
				
			||||||
@@ -23,4 +21,4 @@ const movieCreditsController = (req, res) => {
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = movieCreditsController;
 | 
					module.exports = movieCreditsController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,19 @@
 | 
				
			|||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					const TMDB = require("src/tmdb/tmdb");
 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const Plex = require("src/plex/plex");
 | 
				
			||||||
const Plex = require('src/plex/plex');
 | 
					const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
 | 
				
			||||||
const cache = new Cache();
 | 
					const plex = new Plex(configuration.get("plex", "ip"));
 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					 | 
				
			||||||
const plex = new Plex(configuration.get('plex', 'ip'));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleError(error, res) {
 | 
					function handleError(error, res) {
 | 
				
			||||||
  const { status, message } = error;
 | 
					  const { status, message } = error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (status && message) {
 | 
					  if (status && message) {
 | 
				
			||||||
    res.status(status).send({ success: false, message })
 | 
					    res.status(status).send({ success: false, message });
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    console.log('caught movieinfo controller error', error)
 | 
					    console.log("caught movieinfo controller error", error);
 | 
				
			||||||
    res.status(500).send({ message: 'An unexpected error occured while requesting movie info'})
 | 
					    res.status(500).send({
 | 
				
			||||||
 | 
					      message: "An unexpected error occured while requesting movie info"
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -27,31 +27,44 @@ async function movieInfoController(req, res) {
 | 
				
			|||||||
  const movieId = req.params.id;
 | 
					  const movieId = req.params.id;
 | 
				
			||||||
  let { credits, release_dates, check_existance } = req.query;
 | 
					  let { credits, release_dates, check_existance } = req.query;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  credits && credits.toLowerCase() === 'true' ? credits = true : credits = false
 | 
					  credits && credits.toLowerCase() === "true"
 | 
				
			||||||
  release_dates && release_dates.toLowerCase() === 'true' ? release_dates = true : release_dates = false
 | 
					    ? (credits = true)
 | 
				
			||||||
  check_existance && check_existance.toLowerCase() === 'true' ? check_existance = true : check_existance = false
 | 
					    : (credits = false);
 | 
				
			||||||
 | 
					  release_dates && release_dates.toLowerCase() === "true"
 | 
				
			||||||
 | 
					    ? (release_dates = true)
 | 
				
			||||||
 | 
					    : (release_dates = false);
 | 
				
			||||||
 | 
					  check_existance && check_existance.toLowerCase() === "true"
 | 
				
			||||||
 | 
					    ? (check_existance = true)
 | 
				
			||||||
 | 
					    : (check_existance = false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let tmdbQueue = [tmdb.movieInfo(movieId)]
 | 
					  let tmdbQueue = [tmdb.movieInfo(movieId)];
 | 
				
			||||||
  if (credits)
 | 
					  if (credits) tmdbQueue.push(tmdb.movieCredits(movieId));
 | 
				
			||||||
    tmdbQueue.push(tmdb.movieCredits(movieId))
 | 
					  if (release_dates) tmdbQueue.push(tmdb.movieReleaseDates(movieId));
 | 
				
			||||||
  if (release_dates)
 | 
					 | 
				
			||||||
    tmdbQueue.push(tmdb.movieReleaseDates(movieId))
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  try {
 | 
					  try {
 | 
				
			||||||
    const [ Movie, Credits, ReleaseDates ] = await Promise.all(tmdbQueue)
 | 
					    const [Movie, Credits, ReleaseDates] = await Promise.all(tmdbQueue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    const movie = Movie.createJsonResponse()
 | 
					    const movie = Movie.createJsonResponse();
 | 
				
			||||||
    if (Credits)
 | 
					    if (Credits) movie.credits = Credits.createJsonResponse();
 | 
				
			||||||
      movie.credits = Credits.createJsonResponse()
 | 
					 | 
				
			||||||
    if (ReleaseDates)
 | 
					    if (ReleaseDates)
 | 
				
			||||||
      movie.release_dates = ReleaseDates.createJsonResponse().results
 | 
					      movie.release_dates = ReleaseDates.createJsonResponse().results;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (check_existance)
 | 
					    if (check_existance) {
 | 
				
			||||||
      movie.exists_in_plex = await plex.existsInPlex(movie)
 | 
					      try {
 | 
				
			||||||
  
 | 
					        movie.exists_in_plex = await plex.existsInPlex(movie);
 | 
				
			||||||
    res.send(movie)
 | 
					      } catch (error) {
 | 
				
			||||||
  } catch(error) {
 | 
					        if (error.status === 401) {
 | 
				
			||||||
    handleError(error, res)
 | 
					          console.log("Unathorized request, check plex server LAN settings");
 | 
				
			||||||
 | 
					        } else {
 | 
				
			||||||
 | 
					          console.log("Unkown error from plex!");
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        console.log(error);
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    res.send(movie);
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    handleError(error, res);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,7 @@
 | 
				
			|||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require('src/config/configuration').getInstance();
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const TMDB = require('src/tmdb/tmdb');
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cache = new Cache();
 | 
					const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const movieReleaseDatesController = (req, res) => {
 | 
					const movieReleaseDatesController = (req, res) => {
 | 
				
			||||||
  const movieId = req.params.id;
 | 
					  const movieId = req.params.id;
 | 
				
			||||||
@@ -23,4 +21,4 @@ const movieReleaseDatesController = (req, res) => {
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = movieReleaseDatesController;
 | 
					module.exports = movieReleaseDatesController;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										26
									
								
								seasoned_api/src/webserver/controllers/person/credits.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										26
									
								
								seasoned_api/src/webserver/controllers/person/credits.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,26 @@
 | 
				
			|||||||
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
 | 
					const TMDB = require("src/tmdb/tmdb");
 | 
				
			||||||
 | 
					const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const personCreditsController = (req, res) => {
 | 
				
			||||||
 | 
					  const personId = req.params.id;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return tmdb
 | 
				
			||||||
 | 
					    .personCredits(personId)
 | 
				
			||||||
 | 
					    .then(credits => res.send(credits))
 | 
				
			||||||
 | 
					    .catch(error => {
 | 
				
			||||||
 | 
					      const { status, message } = error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if (status && message) {
 | 
				
			||||||
 | 
					        res.status(status).send({ success: false, message });
 | 
				
			||||||
 | 
					      } else {
 | 
				
			||||||
 | 
					        // TODO log unhandled errors
 | 
				
			||||||
 | 
					        console.log("caugth show credits controller error", error);
 | 
				
			||||||
 | 
					        res.status(500).send({
 | 
				
			||||||
 | 
					          message: "An unexpected error occured while requesting person credits"
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = personCreditsController;
 | 
				
			||||||
@@ -1,25 +1,49 @@
 | 
				
			|||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					const TMDB = require("src/tmdb/tmdb");
 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
 | 
				
			||||||
const cache = new Cache();
 | 
					
 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					function handleError(error, res) {
 | 
				
			||||||
 | 
					  const { status, message } = error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (status && message) {
 | 
				
			||||||
 | 
					    res.status(status).send({ success: false, message });
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    console.log("caught personinfo controller error", error);
 | 
				
			||||||
 | 
					    res.status(500).send({
 | 
				
			||||||
 | 
					      message: "An unexpected error occured while requesting person info."
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Controller: Retrieve information for a person 
 | 
					 * Controller: Retrieve information for a person
 | 
				
			||||||
 * @param {Request} req http request variable
 | 
					 * @param {Request} req http request variable
 | 
				
			||||||
 * @param {Response} res
 | 
					 * @param {Response} res
 | 
				
			||||||
 * @returns {Callback}
 | 
					 * @returns {Callback}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function personInfoController(req, res) {
 | 
					async function personInfoController(req, res) {
 | 
				
			||||||
  const personId = req.params.id;
 | 
					  const personId = req.params.id;
 | 
				
			||||||
 | 
					  let { credits } = req.query;
 | 
				
			||||||
 | 
					  arguments;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  credits && credits.toLowerCase() === "true"
 | 
				
			||||||
 | 
					    ? (credits = true)
 | 
				
			||||||
 | 
					    : (credits = false);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  tmdb.personInfo(personId)
 | 
					  let tmdbQueue = [tmdb.personInfo(personId)];
 | 
				
			||||||
  .then(person => res.send(person.createJsonResponse()))
 | 
					  if (credits) tmdbQueue.push(tmdb.personCredits(personId));
 | 
				
			||||||
  .catch(error => {
 | 
					
 | 
				
			||||||
    res.status(404).send({ success: false, message: error.message });
 | 
					  try {
 | 
				
			||||||
  });
 | 
					    const [Person, Credits] = await Promise.all(tmdbQueue);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const person = Person.createJsonResponse();
 | 
				
			||||||
 | 
					    if (credits) person.credits = Credits.createJsonResponse();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return res.send(person);
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    handleError(error, res);
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = personInfoController;
 | 
					module.exports = personInfoController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
/*
 | 
					/*
 | 
				
			||||||
* @Author: KevinMidboe
 | 
					 * @Author: KevinMidboe
 | 
				
			||||||
* @Date:   2017-10-21 09:54:31
 | 
					 * @Date:   2017-10-21 09:54:31
 | 
				
			||||||
* @Last Modified by:   KevinMidboe
 | 
					 * @Last Modified by:   KevinMidboe
 | 
				
			||||||
* @Last Modified time: 2018-02-26 19:56:32
 | 
					 * @Last Modified time: 2018-02-26 19:56:32
 | 
				
			||||||
*/
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const PirateRepository = require('src/pirate/pirateRepository');
 | 
					const PirateRepository = require("src/pirate/pirateRepository");
 | 
				
			||||||
// const pirateRepository = new PirateRepository();
 | 
					// const pirateRepository = new PirateRepository();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -15,15 +15,15 @@ const PirateRepository = require('src/pirate/pirateRepository');
 | 
				
			|||||||
 * @returns {Callback}
 | 
					 * @returns {Callback}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function updateRequested(req, res) {
 | 
					function updateRequested(req, res) {
 | 
				
			||||||
   const { query, page, type } = req.query;
 | 
					  const { query, page, type } = req.query;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   PirateRepository.SearchPiratebay(query, page, type)
 | 
					  PirateRepository.SearchPiratebay(query, page, type)
 | 
				
			||||||
      .then((result) => {
 | 
					    .then(result => {
 | 
				
			||||||
         res.send({ success: true, results: result });
 | 
					      res.send({ success: true, results: result });
 | 
				
			||||||
      })
 | 
					    })
 | 
				
			||||||
      .catch(error => {
 | 
					    .catch(error => {
 | 
				
			||||||
         res.status(401).send({ success: false, message: error.message });
 | 
					      res.status(401).send({ success: false, message: error.message });
 | 
				
			||||||
      });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = updateRequested;
 | 
					module.exports = updateRequested;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,26 +1,24 @@
 | 
				
			|||||||
const SearchHistory = require('src/searchHistory/searchHistory');
 | 
					const SearchHistory = require("src/searchHistory/searchHistory");
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					const Cache = require("src/tmdb/cache");
 | 
				
			||||||
const RequestRepository = require('src/plex/requestRepository.js');
 | 
					const RequestRepository = require("src/plex/requestRepository.js");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cache = new Cache();
 | 
					const cache = new Cache();
 | 
				
			||||||
const requestRepository = new RequestRepository(cache);
 | 
					const requestRepository = new RequestRepository(cache);
 | 
				
			||||||
const searchHistory = new SearchHistory();
 | 
					const searchHistory = new SearchHistory();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
function searchRequestController(req, res) {
 | 
					function searchRequestController(req, res) {
 | 
				
			||||||
   const user = req.loggedInUser;
 | 
					  const { query, page, type } = req.query;
 | 
				
			||||||
   const { query, page, type } = req.query;
 | 
					  const username = req.loggedInUser ? req.loggedInUser.username : null;
 | 
				
			||||||
   const username = user === undefined ? undefined : user.username;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
   Promise.resolve()
 | 
					  Promise.resolve()
 | 
				
			||||||
      .then(() => searchHistory.create(username, query))
 | 
					    .then(() => searchHistory.create(username, query))
 | 
				
			||||||
      .then(() => requestRepository.search(query, page, type))
 | 
					    .then(() => requestRepository.search(query, page, type))
 | 
				
			||||||
      .then((searchResult) => {
 | 
					    .then(searchResult => {
 | 
				
			||||||
         res.send(searchResult);
 | 
					      res.send(searchResult);
 | 
				
			||||||
      })
 | 
					    })
 | 
				
			||||||
      .catch(error => {
 | 
					    .catch(error => {
 | 
				
			||||||
         res.status(500).send({ success: false, message: error.message });
 | 
					      res.status(500).send({ success: false, message: error.message });
 | 
				
			||||||
      });
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = searchRequestController;
 | 
					module.exports = searchRequestController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,19 +1,16 @@
 | 
				
			|||||||
const configuration = require('src/config/configuration').getInstance()
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
const RequestRepository = require('src/request/request');
 | 
					const RequestRepository = require("src/request/request");
 | 
				
			||||||
const Cache = require('src/tmdb/cache')
 | 
					const TMDB = require("src/tmdb/tmdb");
 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb')
 | 
					const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
 | 
				
			||||||
 | 
					const request = new RequestRepository();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const cache = new Cache()
 | 
					const tmdbMovieInfo = id => {
 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'))
 | 
					  return tmdb.movieInfo(id);
 | 
				
			||||||
const request = new RequestRepository()
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tmdbMovieInfo = (id) => {
 | 
					const tmdbShowInfo = id => {
 | 
				
			||||||
  return tmdb.movieInfo(id)
 | 
					  return tmdb.showInfo(id);
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					 | 
				
			||||||
const tmdbShowInfo = (id) => {
 | 
					 | 
				
			||||||
  return tmdb.showInfo(id)
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Controller: POST a media id to be donwloaded
 | 
					 * Controller: POST a media id to be donwloaded
 | 
				
			||||||
@@ -24,28 +21,43 @@ const tmdbShowInfo = (id) => {
 | 
				
			|||||||
function submitRequestController(req, res) {
 | 
					function submitRequestController(req, res) {
 | 
				
			||||||
  // This is the id that is the param of the url
 | 
					  // This is the id that is the param of the url
 | 
				
			||||||
  const id = req.params.mediaId;
 | 
					  const id = req.params.mediaId;
 | 
				
			||||||
  const type = req.query.type ? req.query.type.toLowerCase() : undefined
 | 
					  const type = req.query.type ? req.query.type.toLowerCase() : undefined;
 | 
				
			||||||
  const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
 | 
					  const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress;
 | 
				
			||||||
  const user_agent = req.headers['user-agent'];
 | 
					  const user_agent = req.headers["user-agent"];
 | 
				
			||||||
  const user = req.loggedInUser;
 | 
					  const username = req.loggedInUser ? req.loggedInUser.username : null;
 | 
				
			||||||
  let mediaFunction = undefined
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (type === 'movie') {
 | 
					  let mediaFunction = undefined;
 | 
				
			||||||
    console.log('movie')
 | 
					
 | 
				
			||||||
    mediaFunction = tmdbMovieInfo
 | 
					  if (type === "movie") {
 | 
				
			||||||
  } else if (type === 'show') {
 | 
					    console.log("movie");
 | 
				
			||||||
    console.log('show')
 | 
					    mediaFunction = tmdbMovieInfo;
 | 
				
			||||||
    mediaFunction = tmdbShowInfo
 | 
					  } else if (type === "show") {
 | 
				
			||||||
 | 
					    console.log("show");
 | 
				
			||||||
 | 
					    mediaFunction = tmdbShowInfo;
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    res.status(422).send({ success: false, message: 'Incorrect type. Allowed types: "movie" or "show"'})
 | 
					    res
 | 
				
			||||||
 | 
					      .status(422)
 | 
				
			||||||
 | 
					      .send({
 | 
				
			||||||
 | 
					        success: false,
 | 
				
			||||||
 | 
					        message: 'Incorrect type. Allowed types: "movie" or "show"'
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (mediaFunction === undefined) { res.status(200); return }
 | 
					  if (mediaFunction === undefined) {
 | 
				
			||||||
 | 
					    res.status(200);
 | 
				
			||||||
 | 
					    return;
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  mediaFunction(id)
 | 
					  mediaFunction(id)
 | 
				
			||||||
    .then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user))
 | 
					    .then(tmdbMedia =>
 | 
				
			||||||
    .then(() => res.send({ success: true, message: 'Media item successfully requested' }))
 | 
					      request.requestFromTmdb(tmdbMedia, ip, user_agent, username)
 | 
				
			||||||
    .catch(err => res.status(500).send({ success: false, message: err.message }))
 | 
					    )
 | 
				
			||||||
 | 
					    .then(() =>
 | 
				
			||||||
 | 
					      res.send({ success: true, message: "Media item successfully requested" })
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .catch(err =>
 | 
				
			||||||
 | 
					      res.status(500).send({ success: false, message: err.message })
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = submitRequestController;
 | 
					module.exports = submitRequestController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					const configuration = require('src/config/configuration').getInstance();
 | 
				
			||||||
 | 
					const Plex = require('src/plex/plex');
 | 
				
			||||||
 | 
					const plex = new Plex(configuration.get('plex', 'ip'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Controller: Search plex for movies, shows and episodes by query
 | 
				
			||||||
 | 
					 * @param {Request} req http request variable
 | 
				
			||||||
 | 
					 * @param {Response} res
 | 
				
			||||||
 | 
					 * @returns {Callback}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					function watchDirectLink (req, res) {
 | 
				
			||||||
 | 
					   const { title, year } = req.query;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  plex.getDirectLinkByTitleAndYear(title, year)
 | 
				
			||||||
 | 
					    .then(plexDirectLink => {
 | 
				
			||||||
 | 
					      if (plexDirectLink == false)
 | 
				
			||||||
 | 
					        res.status(404).send({ success: true, link: null })
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        res.status(200).send({ success: true, link: plexDirectLink })
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch(error => {
 | 
				
			||||||
 | 
					       res.status(500).send({ success: false, message: error.message });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = watchDirectLink;
 | 
				
			||||||
@@ -1,18 +1,17 @@
 | 
				
			|||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					const TMDB = require("src/tmdb/tmdb");
 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const RequestRepository = require("src/request/request");
 | 
				
			||||||
const RequestRepository = require('src/request/request');
 | 
					const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
 | 
				
			||||||
const cache = new Cache();
 | 
					 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					 | 
				
			||||||
const request = new RequestRepository();
 | 
					const request = new RequestRepository();
 | 
				
			||||||
 | 
					// const { sendSMS } = require("src/notifications/sms");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tmdbMovieInfo = (id) => {
 | 
					const tmdbMovieInfo = id => {
 | 
				
			||||||
  return tmdb.movieInfo(id)
 | 
					  return tmdb.movieInfo(id);
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const tmdbShowInfo = (id) => {
 | 
					const tmdbShowInfo = id => {
 | 
				
			||||||
  return tmdb.showInfo(id)
 | 
					  return tmdb.showInfo(id);
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Controller: Request by id with type param
 | 
					 * Controller: Request by id with type param
 | 
				
			||||||
@@ -21,33 +20,48 @@ const tmdbShowInfo = (id) => {
 | 
				
			|||||||
 * @returns {Callback}
 | 
					 * @returns {Callback}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function requestTmdbIdController(req, res) {
 | 
					function requestTmdbIdController(req, res) {
 | 
				
			||||||
  const { id, type } = req.body
 | 
					  const { id, type } = req.body;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const ip = req.headers['x-forwarded-for'] || req.connection.remoteAddress;
 | 
					  const ip = req.headers["x-forwarded-for"] || req.connection.remoteAddress;
 | 
				
			||||||
  const user_agent = req.headers['user-agent'];
 | 
					  const user_agent = req.headers["user-agent"];
 | 
				
			||||||
  const user = req.loggedInUser;
 | 
					  const username = req.loggedInUser ? req.loggedInUser.username : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  let mediaFunction = undefined
 | 
					  let mediaFunction = undefined;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (id === undefined || type === undefined) {
 | 
					  if (id === undefined || type === undefined) {
 | 
				
			||||||
    res.status(422).send({ success: false, message: "'Missing parameteres: 'id' and/or 'type'"})
 | 
					    res.status(422).send({
 | 
				
			||||||
 | 
					      success: false,
 | 
				
			||||||
 | 
					      message: "'Missing parameteres: 'id' and/or 'type'"
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (type === 'movie') {
 | 
					  if (type === "movie") {
 | 
				
			||||||
    mediaFunction = tmdbMovieInfo
 | 
					    mediaFunction = tmdbMovieInfo;
 | 
				
			||||||
  } else if (type === 'show') {
 | 
					  } else if (type === "show") {
 | 
				
			||||||
    mediaFunction = tmdbShowInfo
 | 
					    mediaFunction = tmdbShowInfo;
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    res.status(422).send({ success: false, message: 'Incorrect type. Allowed types: "movie" or "show"'})
 | 
					    res.status(422).send({
 | 
				
			||||||
 | 
					      success: false,
 | 
				
			||||||
 | 
					      message: 'Incorrect type. Allowed types: "movie" or "show"'
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  mediaFunction(id)
 | 
					  mediaFunction(id)
 | 
				
			||||||
    // .catch((error) => { console.error(error); res.status(404).send({ success: false, error: 'Id not found' }) })
 | 
					    // .catch((error) => { console.error(error); res.status(404).send({ success: false, error: 'Id not found' }) })
 | 
				
			||||||
    .then(tmdbMedia => request.requestFromTmdb(tmdbMedia, ip, user_agent, user))
 | 
					    .then(tmdbMedia => {
 | 
				
			||||||
    .then(() => res.send({success: true, message: 'Request has been submitted.'}))
 | 
					      request.requestFromTmdb(tmdbMedia, ip, user_agent, username);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      // TODO enable SMS
 | 
				
			||||||
 | 
					      // const url = `https://request.movie?${tmdbMedia.type}=${tmdbMedia.id}`;
 | 
				
			||||||
 | 
					      // const message = `${tmdbMedia.title} (${tmdbMedia.year}) requested!\n${url}`;
 | 
				
			||||||
 | 
					      // sendSMS(message);
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .then(() =>
 | 
				
			||||||
 | 
					      res.send({ success: true, message: "Request has been submitted." })
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
    .catch(error => {
 | 
					    .catch(error => {
 | 
				
			||||||
      res.send({ success: false, message: error.message });
 | 
					      res.send({ success: false, message: error.message });
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = requestTmdbIdController;
 | 
					module.exports = requestTmdbIdController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,7 @@
 | 
				
			|||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					const TMDB = require("src/tmdb/tmdb");
 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const SearchHistory = require("src/searchHistory/searchHistory");
 | 
				
			||||||
const SearchHistory = require('src/searchHistory/searchHistory');
 | 
					const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
 | 
				
			||||||
const cache = new Cache();
 | 
					 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					 | 
				
			||||||
const searchHistory = new SearchHistory();
 | 
					const searchHistory = new SearchHistory();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -13,28 +11,30 @@ const searchHistory = new SearchHistory();
 | 
				
			|||||||
 * @returns {Callback}
 | 
					 * @returns {Callback}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function movieSearchController(req, res) {
 | 
					function movieSearchController(req, res) {
 | 
				
			||||||
  const user = req.loggedInUser;
 | 
					  const { query, page, adult } = req.query;
 | 
				
			||||||
  const { query, page } = req.query;
 | 
					  const username = req.loggedInUser ? req.loggedInUser.username : null;
 | 
				
			||||||
 | 
					  const includeAdult = adult == "true" ? true : false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (user) {
 | 
					  if (username) {
 | 
				
			||||||
    return searchHistory.create(user, query);
 | 
					    searchHistory.create(username, query);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  tmdb.movieSearch(query, page)
 | 
					  return tmdb
 | 
				
			||||||
 | 
					    .movieSearch(query, page, includeAdult)
 | 
				
			||||||
    .then(movieSearchResults => res.send(movieSearchResults))
 | 
					    .then(movieSearchResults => res.send(movieSearchResults))
 | 
				
			||||||
    .catch(error => {
 | 
					    .catch(error => {
 | 
				
			||||||
      const { status, message } = error;
 | 
					      const { status, message } = error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (status && message) {
 | 
					      if (status && message) {
 | 
				
			||||||
        res.status(status).send({ success: false, message })
 | 
					        res.status(status).send({ success: false, message });
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        // TODO log unhandled errors
 | 
					        // TODO log unhandled errors
 | 
				
			||||||
        console.log('caugth movie search controller error', error)
 | 
					        console.log("caugth movie search controller error", error);
 | 
				
			||||||
        res.status(500).send({
 | 
					        res.status(500).send({
 | 
				
			||||||
          message: `An unexpected error occured while searching movies with query: ${query}`
 | 
					          message: `An unexpected error occured while searching movies with query: ${query}`
 | 
				
			||||||
        })
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = movieSearchController;
 | 
					module.exports = movieSearchController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,16 +1,14 @@
 | 
				
			|||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					const TMDB = require("src/tmdb/tmdb");
 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const SearchHistory = require("src/searchHistory/searchHistory");
 | 
				
			||||||
const SearchHistory = require('src/searchHistory/searchHistory');
 | 
					const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
 | 
				
			||||||
const cache = new Cache();
 | 
					 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					 | 
				
			||||||
const searchHistory = new SearchHistory();
 | 
					const searchHistory = new SearchHistory();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function checkAndCreateJsonResponse(result) {
 | 
					function checkAndCreateJsonResponse(result) {
 | 
				
			||||||
  if (typeof result['createJsonResponse'] === 'function') {
 | 
					  if (typeof result["createJsonResponse"] === "function") {
 | 
				
			||||||
    return result.createJsonResponse()
 | 
					    return result.createJsonResponse();
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  return result
 | 
					  return result;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -20,26 +18,31 @@ function checkAndCreateJsonResponse(result) {
 | 
				
			|||||||
 * @returns {Callback}
 | 
					 * @returns {Callback}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function multiSearchController(req, res) {
 | 
					function multiSearchController(req, res) {
 | 
				
			||||||
  const user = req.loggedInUser;
 | 
					  const { query, page, adult } = req.query;
 | 
				
			||||||
  const { query, page } = req.query;
 | 
					  const username = req.loggedInUser ? req.loggedInUser.username : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (user) {
 | 
					  if (username) {
 | 
				
			||||||
    searchHistory.create(user.username, query)
 | 
					    searchHistory.create(username, query);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return tmdb.multiSearch(query, page)
 | 
					  return tmdb
 | 
				
			||||||
  .then(multiSearchResults => res.send(multiSearchResults))
 | 
					    .multiSearch(query, page, adult)
 | 
				
			||||||
  .catch(error => {
 | 
					    .then(multiSearchResults => res.send(multiSearchResults))
 | 
				
			||||||
    const { status, message } = error;
 | 
					    .catch(error => {
 | 
				
			||||||
 | 
					      const { status, message } = error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (status && message) {
 | 
					      if (status && message) {
 | 
				
			||||||
      res.status(status).send({ success: false, message })
 | 
					        res.status(status).send({ success: false, message });
 | 
				
			||||||
    } else {
 | 
					      } else {
 | 
				
			||||||
      // TODO log unhandled errors
 | 
					        // TODO log unhandled errors
 | 
				
			||||||
      console.log('caugth multi search controller error', error)
 | 
					        console.log("caugth multi search controller error", error);
 | 
				
			||||||
      res.status(500).send({ message: `An unexpected error occured while searching with query: ${query}` })
 | 
					        res
 | 
				
			||||||
    }
 | 
					          .status(500)
 | 
				
			||||||
  })
 | 
					          .send({
 | 
				
			||||||
 | 
					            message: `An unexpected error occured while searching with query: ${query}`
 | 
				
			||||||
 | 
					          });
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = multiSearchController;
 | 
					module.exports = multiSearchController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,7 @@
 | 
				
			|||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					const TMDB = require("src/tmdb/tmdb");
 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const SearchHistory = require("src/searchHistory/searchHistory");
 | 
				
			||||||
const SearchHistory = require('src/searchHistory/searchHistory');
 | 
					const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
 | 
				
			||||||
const cache = new Cache();
 | 
					 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					 | 
				
			||||||
const searchHistory = new SearchHistory();
 | 
					const searchHistory = new SearchHistory();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -13,30 +11,30 @@ const searchHistory = new SearchHistory();
 | 
				
			|||||||
 * @returns {Callback}
 | 
					 * @returns {Callback}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function personSearchController(req, res) {
 | 
					function personSearchController(req, res) {
 | 
				
			||||||
  const user = req.loggedInUser;
 | 
					  const { query, page, adult } = req.query;
 | 
				
			||||||
  const { query, page } = req.query;
 | 
					  const username = req.loggedInUser ? req.loggedInUser.username : null;
 | 
				
			||||||
 | 
					  const includeAdult = adult == "true" ? true : false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (user) {
 | 
					  if (username) {
 | 
				
			||||||
    return searchHistory.create(user, query);
 | 
					    searchHistory.create(username, query);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
  
 | 
					
 | 
				
			||||||
  tmdb.personSearch(query, page)
 | 
					  return tmdb
 | 
				
			||||||
    .then((person) => {
 | 
					    .personSearch(query, page, includeAdult)
 | 
				
			||||||
      res.send(person);
 | 
					    .then(persons => res.send(persons))
 | 
				
			||||||
    })
 | 
					 | 
				
			||||||
    .catch(error => {
 | 
					    .catch(error => {
 | 
				
			||||||
      const { status, message } = error;
 | 
					      const { status, message } = error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if (status && message) {
 | 
					      if (status && message) {
 | 
				
			||||||
        res.status(status).send({ success: false, message })
 | 
					        res.status(status).send({ success: false, message });
 | 
				
			||||||
      } else {
 | 
					      } else {
 | 
				
			||||||
        // TODO log unhandled errors
 | 
					        // TODO log unhandled errors
 | 
				
			||||||
        console.log('caugth person search controller error', error)
 | 
					        console.log("caugth person search controller error", error);
 | 
				
			||||||
        res.status(500).send({
 | 
					        res.status(500).send({
 | 
				
			||||||
          message: `An unexpected error occured while searching people with query: ${query}`
 | 
					          message: `An unexpected error occured while searching people with query: ${query}`
 | 
				
			||||||
        })
 | 
					        });
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = personSearchController;
 | 
					module.exports = personSearchController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,7 @@
 | 
				
			|||||||
const SearchHistory = require('src/searchHistory/searchHistory');
 | 
					const SearchHistory = require("src/searchHistory/searchHistory");
 | 
				
			||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					const TMDB = require("src/tmdb/tmdb");
 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const tmdb = new TMDB(configuration.get("tmdb", "apiKey"));
 | 
				
			||||||
const cache = new Cache();
 | 
					 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					 | 
				
			||||||
const searchHistory = new SearchHistory();
 | 
					const searchHistory = new SearchHistory();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
@@ -13,23 +11,22 @@ const searchHistory = new SearchHistory();
 | 
				
			|||||||
 * @returns {Callback}
 | 
					 * @returns {Callback}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function showSearchController(req, res) {
 | 
					function showSearchController(req, res) {
 | 
				
			||||||
  const user = req.loggedInUser;
 | 
					  const { query, page, adult } = req.query;
 | 
				
			||||||
  const { query, page } = req.query;
 | 
					  const username = req.loggedInUser ? req.loggedInUser.username : null;
 | 
				
			||||||
 | 
					  const includeAdult = adult == "true" ? true : false;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  Promise.resolve()
 | 
					  if (username) {
 | 
				
			||||||
  .then(() => {
 | 
					    searchHistory.create(username, query);
 | 
				
			||||||
    if (user) {
 | 
					  }
 | 
				
			||||||
      return searchHistory.create(user, query);
 | 
					
 | 
				
			||||||
    }
 | 
					  return tmdb
 | 
				
			||||||
    return null
 | 
					    .showSearch(query, page, includeAdult)
 | 
				
			||||||
  })
 | 
					    .then(shows => {
 | 
				
			||||||
  .then(() => tmdb.showSearch(query, page))
 | 
					      res.send(shows);
 | 
				
			||||||
  .then((shows) => {
 | 
					    })
 | 
				
			||||||
    res.send(shows);
 | 
					    .catch(error => {
 | 
				
			||||||
  })
 | 
					      res.status(500).send({ success: false, message: error.message });
 | 
				
			||||||
  .catch(error => {
 | 
					    });
 | 
				
			||||||
    res.status(500).send({ success: false, message: error.message });
 | 
					 | 
				
			||||||
  });
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = showSearchController;
 | 
					module.exports = showSearchController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,6 @@
 | 
				
			|||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require('src/config/configuration').getInstance();
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const TMDB = require('src/tmdb/tmdb');
 | 
				
			||||||
 | 
					const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
 | 
				
			||||||
const cache = new Cache();
 | 
					 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
const showCreditsController = (req, res) => {
 | 
					const showCreditsController = (req, res) => {
 | 
				
			||||||
  const showId = req.params.id;
 | 
					  const showId = req.params.id;
 | 
				
			||||||
@@ -23,4 +20,4 @@ const showCreditsController = (req, res) => {
 | 
				
			|||||||
    })
 | 
					    })
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = showCreditsController;
 | 
					module.exports = showCreditsController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,7 @@
 | 
				
			|||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require('src/config/configuration').getInstance();
 | 
				
			||||||
const Cache = require('src/tmdb/cache');
 | 
					 | 
				
			||||||
const TMDB = require('src/tmdb/tmdb');
 | 
					const TMDB = require('src/tmdb/tmdb');
 | 
				
			||||||
const Plex = require('src/plex/plex');
 | 
					const Plex = require('src/plex/plex');
 | 
				
			||||||
const cache = new Cache();
 | 
					const tmdb = new TMDB(configuration.get('tmdb', 'apiKey'));
 | 
				
			||||||
const tmdb = new TMDB(cache, configuration.get('tmdb', 'apiKey'));
 | 
					 | 
				
			||||||
const plex = new Plex(configuration.get('plex', 'ip'));
 | 
					const plex = new Plex(configuration.get('plex', 'ip'));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleError(error, res) {
 | 
					function handleError(error, res) {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,24 +1,25 @@
 | 
				
			|||||||
const UserRepository = require('src/user/userRepository');
 | 
					const UserRepository = require("src/user/userRepository");
 | 
				
			||||||
const userRepository = new UserRepository();
 | 
					const userRepository = new UserRepository();
 | 
				
			||||||
 const fetch = require('node-fetch');
 | 
					const fetch = require("node-fetch");
 | 
				
			||||||
 const FormData = require('form-data');
 | 
					const FormData = require("form-data");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleError(error, res) {
 | 
					function handleError(error, res) {
 | 
				
			||||||
  let { status, message, source } = error;
 | 
					  let { status, message, source } = error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (status && message) {
 | 
					  if (status && message) {
 | 
				
			||||||
    if (status === 401) {
 | 
					    if (status === 401) {
 | 
				
			||||||
      message = 'Unauthorized. Please check plex credentials.',
 | 
					      (message = "Unauthorized. Please check plex credentials."),
 | 
				
			||||||
      source = 'plex'
 | 
					        (source = "plex");
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    res.status(status).send({ success: false, message, source })
 | 
					    res.status(status).send({ success: false, message, source });
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    console.log('caught authenticate plex account controller error', error)
 | 
					    console.log("caught authenticate plex account controller error", error);
 | 
				
			||||||
    res.status(500).send({
 | 
					    res.status(500).send({
 | 
				
			||||||
      message: 'An unexpected error occured while authenticating your account with plex',
 | 
					      message:
 | 
				
			||||||
 | 
					        "An unexpected error occured while authenticating your account with plex",
 | 
				
			||||||
      source
 | 
					      source
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -28,33 +29,32 @@ function handleResponse(response) {
 | 
				
			|||||||
      success: false,
 | 
					      success: false,
 | 
				
			||||||
      status: response.status,
 | 
					      status: response.status,
 | 
				
			||||||
      message: response.statusText
 | 
					      message: response.statusText
 | 
				
			||||||
    }
 | 
					    };
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return response.json()
 | 
					  return response.json();
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function plexAuthenticate(username, password) {
 | 
					function plexAuthenticate(username, password) {
 | 
				
			||||||
  const url = 'https://plex.tv/api/v2/users/signin'
 | 
					  const url = "https://plex.tv/api/v2/users/signin";
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const form = new FormData()
 | 
					  const form = new FormData();
 | 
				
			||||||
  form.append('login', username)
 | 
					  form.append("login", username);
 | 
				
			||||||
  form.append('password', password)
 | 
					  form.append("password", password);
 | 
				
			||||||
  form.append('rememberMe', 'false')
 | 
					  form.append("rememberMe", "false");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const headers = {
 | 
					  const headers = {
 | 
				
			||||||
    'Accept': 'application/json, text/plain, */*',
 | 
					    Accept: "application/json, text/plain, */*",
 | 
				
			||||||
    'Content-Type': form.getHeaders()['content-type'],
 | 
					    "Content-Type": form.getHeaders()["content-type"],
 | 
				
			||||||
    'X-Plex-Client-Identifier': 'seasonedRequest'
 | 
					    "X-Plex-Client-Identifier": "seasonedRequest"
 | 
				
			||||||
  }
 | 
					  };
 | 
				
			||||||
  const options = {
 | 
					  const options = {
 | 
				
			||||||
    method: 'POST',
 | 
					    method: "POST",
 | 
				
			||||||
    headers,
 | 
					    headers,
 | 
				
			||||||
    body: form
 | 
					    body: form
 | 
				
			||||||
  }
 | 
					  };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return fetch(url, options)
 | 
					  return fetch(url, options).then(resp => handleResponse(resp));
 | 
				
			||||||
    .then(resp => handleResponse(resp))
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function link(req, res) {
 | 
					function link(req, res) {
 | 
				
			||||||
@@ -63,22 +63,28 @@ function link(req, res) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
  return plexAuthenticate(username, password)
 | 
					  return plexAuthenticate(username, password)
 | 
				
			||||||
    .then(plexUser => userRepository.linkPlexUserId(user.username, plexUser.id))
 | 
					    .then(plexUser => userRepository.linkPlexUserId(user.username, plexUser.id))
 | 
				
			||||||
    .then(response => res.send({
 | 
					    .then(response =>
 | 
				
			||||||
      success: true,
 | 
					      res.send({
 | 
				
			||||||
      message: "Successfully authenticated and linked plex account with seasoned request."
 | 
					        success: true,
 | 
				
			||||||
    }))
 | 
					        message:
 | 
				
			||||||
    .catch(error => handleError(error, res))
 | 
					          "Successfully authenticated and linked plex account with seasoned request."
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .catch(error => handleError(error, res));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function unlink(req, res) {
 | 
					function unlink(req, res) {
 | 
				
			||||||
  const user = req.loggedInUser;
 | 
					  const username = req.loggedInUser ? req.loggedInUser.username : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  return userRepository.unlinkPlexUserId(user.username)
 | 
					  return userRepository
 | 
				
			||||||
    .then(response => res.send({
 | 
					    .unlinkPlexUserId(username)
 | 
				
			||||||
      success: true,
 | 
					    .then(response =>
 | 
				
			||||||
      message: "Successfully unlinked plex account from seasoned request."
 | 
					      res.send({
 | 
				
			||||||
    }))
 | 
					        success: true,
 | 
				
			||||||
    .catch(error => handleError(error, res))
 | 
					        message: "Successfully unlinked plex account from seasoned request."
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .catch(error => handleError(error, res));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,36 +1,61 @@
 | 
				
			|||||||
const User = require('src/user/user');
 | 
					const User = require("src/user/user");
 | 
				
			||||||
const Token = require('src/user/token');
 | 
					const Token = require("src/user/token");
 | 
				
			||||||
const UserSecurity = require('src/user/userSecurity');
 | 
					const UserSecurity = require("src/user/userSecurity");
 | 
				
			||||||
const UserRepository = require('src/user/userRepository');
 | 
					const UserRepository = require("src/user/userRepository");
 | 
				
			||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const secret = configuration.get('authentication', 'secret');
 | 
					const secret = configuration.get("authentication", "secret");
 | 
				
			||||||
const userSecurity = new UserSecurity();
 | 
					const userSecurity = new UserSecurity();
 | 
				
			||||||
const userRepository = new UserRepository();
 | 
					const userRepository = new UserRepository();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
// TODO look to move some of the token generation out of the reach of the final "catch-all"
 | 
					// TODO look to move some of the token generation out of the reach of the final "catch-all"
 | 
				
			||||||
// catch including the, maybe sensitive, error message.
 | 
					// catch including the, maybe sensitive, error message.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isProduction = process.env.NODE_ENV === "production";
 | 
				
			||||||
 | 
					const cookieOptions = {
 | 
				
			||||||
 | 
					  httpOnly: false,
 | 
				
			||||||
 | 
					  secure: isProduction,
 | 
				
			||||||
 | 
					  maxAge: 90 * 24 * 3600000, // 90 days
 | 
				
			||||||
 | 
					  sameSite: isProduction ? "Strict" : "Lax"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Controller: Log in a user provided correct credentials.
 | 
					 * Controller: Log in a user provided correct credentials.
 | 
				
			||||||
 * @param {Request} req http request variable
 | 
					 * @param {Request} req http request variable
 | 
				
			||||||
 * @param {Response} res
 | 
					 * @param {Response} res
 | 
				
			||||||
 * @returns {Callback}
 | 
					 * @returns {Callback}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function loginController(req, res) {
 | 
					async function loginController(req, res) {
 | 
				
			||||||
   const user = new User(req.body.username);
 | 
					  const user = new User(req.body.username);
 | 
				
			||||||
   const password = req.body.password;
 | 
					  const password = req.body.password;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   userSecurity.login(user, password)
 | 
					  try {
 | 
				
			||||||
      .then(() => userRepository.checkAdmin(user))
 | 
					    const [loggedIn, isAdmin, settings] = await Promise.all([
 | 
				
			||||||
      .then(checkAdmin => {
 | 
					      userSecurity.login(user, password),
 | 
				
			||||||
         const isAdmin = checkAdmin === 1 ? true : false;
 | 
					      userRepository.checkAdmin(user),
 | 
				
			||||||
         const token = new Token(user, isAdmin).toString(secret);
 | 
					      userRepository.getSettings(user.username)
 | 
				
			||||||
         res.send({ success: true, token });
 | 
					    ]);
 | 
				
			||||||
      })
 | 
					
 | 
				
			||||||
      .catch(error => {
 | 
					    if (!loggedIn) {
 | 
				
			||||||
         res.status(401).send({ success: false, message: error.message });
 | 
					      return res.status(503).send({
 | 
				
			||||||
 | 
					        success: false,
 | 
				
			||||||
 | 
					        message: "Unexpected error! Unable to create user."
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const token = new Token(
 | 
				
			||||||
 | 
					      user,
 | 
				
			||||||
 | 
					      isAdmin === 1 ? true : false,
 | 
				
			||||||
 | 
					      settings
 | 
				
			||||||
 | 
					    ).toString(secret);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return res.cookie("authorization", token, cookieOptions).status(200).send({
 | 
				
			||||||
 | 
					      success: true,
 | 
				
			||||||
 | 
					      message: "Welcome to request.movie!"
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					  } catch (error) {
 | 
				
			||||||
 | 
					    return res.status(401).send({ success: false, message: error.message });
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = loginController;
 | 
					module.exports = loginController;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								seasoned_api/src/webserver/controllers/user/logout.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								seasoned_api/src/webserver/controllers/user/logout.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					/**
 | 
				
			||||||
 | 
					 * Controller: Log out a user (destroy authorization token)
 | 
				
			||||||
 | 
					 * @param {Request} req http request variable
 | 
				
			||||||
 | 
					 * @param {Response} res
 | 
				
			||||||
 | 
					 * @returns {Callback}
 | 
				
			||||||
 | 
					 */
 | 
				
			||||||
 | 
					async function logoutController(req, res) {
 | 
				
			||||||
 | 
					  res.clearCookie("authorization");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return res.status(200).send({
 | 
				
			||||||
 | 
					    success: true,
 | 
				
			||||||
 | 
					    message: "Logged out, see you later!"
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = logoutController;
 | 
				
			||||||
@@ -1,13 +1,21 @@
 | 
				
			|||||||
const User = require('src/user/user');
 | 
					const User = require("src/user/user");
 | 
				
			||||||
const Token = require('src/user/token');
 | 
					const Token = require("src/user/token");
 | 
				
			||||||
const UserSecurity = require('src/user/userSecurity');
 | 
					const UserSecurity = require("src/user/userSecurity");
 | 
				
			||||||
const UserRepository = require('src/user/userRepository');
 | 
					const UserRepository = require("src/user/userRepository");
 | 
				
			||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const secret = configuration.get('authentication', 'secret');
 | 
					const secret = configuration.get("authentication", "secret");
 | 
				
			||||||
const userSecurity = new UserSecurity();
 | 
					const userSecurity = new UserSecurity();
 | 
				
			||||||
const userRepository = new UserRepository();
 | 
					const userRepository = new UserRepository();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const isProduction = process.env.NODE_ENV === "production";
 | 
				
			||||||
 | 
					const cookieOptions = {
 | 
				
			||||||
 | 
					  httpOnly: false,
 | 
				
			||||||
 | 
					  secure: isProduction,
 | 
				
			||||||
 | 
					  maxAge: 90 * 24 * 3600000, // 90 days
 | 
				
			||||||
 | 
					  sameSite: isProduction ? "Strict" : "Lax"
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Controller: Register a new user
 | 
					 * Controller: Register a new user
 | 
				
			||||||
 * @param {Request} req http request variable
 | 
					 * @param {Request} req http request variable
 | 
				
			||||||
@@ -15,21 +23,25 @@ const userRepository = new UserRepository();
 | 
				
			|||||||
 * @returns {Callback}
 | 
					 * @returns {Callback}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function registerController(req, res) {
 | 
					function registerController(req, res) {
 | 
				
			||||||
   const user = new User(req.body.username, req.body.email);
 | 
					  const user = new User(req.body.username, req.body.email);
 | 
				
			||||||
   const password = req.body.password;
 | 
					  const password = req.body.password;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   userSecurity.createNewUser(user, password)
 | 
					  userSecurity
 | 
				
			||||||
      .then(() => userRepository.checkAdmin(user))
 | 
					    .createNewUser(user, password)
 | 
				
			||||||
      .then(checkAdmin => {
 | 
					    .then(() => {
 | 
				
			||||||
         const isAdmin = checkAdmin === 1 ? true : false;
 | 
					      const token = new Token(user, false).toString(secret);
 | 
				
			||||||
         const token = new Token(user, isAdmin).toString(secret);
 | 
					
 | 
				
			||||||
         res.send({
 | 
					      return res
 | 
				
			||||||
            success: true, message: 'Welcome to Seasoned!', token
 | 
					        .cookie("authorization", token, cookieOptions)
 | 
				
			||||||
         });
 | 
					        .status(200)
 | 
				
			||||||
      })
 | 
					        .send({
 | 
				
			||||||
      .catch(error => {
 | 
					          success: true,
 | 
				
			||||||
         res.status(401).send({ success: false, message: error.message });
 | 
					          message: "Welcome to Seasoned!"
 | 
				
			||||||
      });
 | 
					        });
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch(error => {
 | 
				
			||||||
 | 
					      res.status(401).send({ success: false, message: error.message });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = registerController;
 | 
					module.exports = registerController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
const RequestRepository = require('src/plex/requestRepository.js');
 | 
					const RequestRepository = require("src/plex/requestRepository.js");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const requestRepository = new RequestRepository();
 | 
					const requestRepository = new RequestRepository();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,15 +9,20 @@ const requestRepository = new RequestRepository();
 | 
				
			|||||||
 * @returns {Callback}
 | 
					 * @returns {Callback}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function requestsController(req, res) {
 | 
					function requestsController(req, res) {
 | 
				
			||||||
   const user = req.loggedInUser;
 | 
					  const username = req.loggedInUser ? req.loggedInUser.username : null;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   requestRepository.userRequests(user)
 | 
					  requestRepository
 | 
				
			||||||
      .then(requests => {
 | 
					    .userRequests(username)
 | 
				
			||||||
         res.send({ success: true, results: requests, total_results: requests.length });
 | 
					    .then(requests => {
 | 
				
			||||||
      })
 | 
					      res.send({
 | 
				
			||||||
      .catch(error => {
 | 
					        success: true,
 | 
				
			||||||
         res.status(500).send({ success: false, message: error.message });
 | 
					        results: requests,
 | 
				
			||||||
 | 
					        total_results: requests.length
 | 
				
			||||||
      });
 | 
					      });
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch(error => {
 | 
				
			||||||
 | 
					      res.status(500).send({ success: false, message: error.message });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = requestsController;
 | 
					module.exports = requestsController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
const SearchHistory = require('src/searchHistory/searchHistory');
 | 
					const SearchHistory = require("src/searchHistory/searchHistory");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const searchHistory = new SearchHistory();
 | 
					const searchHistory = new SearchHistory();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -9,16 +9,16 @@ const searchHistory = new SearchHistory();
 | 
				
			|||||||
 * @returns {Callback}
 | 
					 * @returns {Callback}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
function historyController(req, res) {
 | 
					function historyController(req, res) {
 | 
				
			||||||
   const user = req.loggedInUser;
 | 
					  const username = req.loggedInUser ? req.loggedInUser.username : null;
 | 
				
			||||||
   const username = user === undefined ? undefined : user.username;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
   searchHistory.read(username)
 | 
					  searchHistory
 | 
				
			||||||
      .then(searchQueries => {
 | 
					    .read(username)
 | 
				
			||||||
         res.send({ success: true, searchQueries });
 | 
					    .then(searchQueries => {
 | 
				
			||||||
      })
 | 
					      res.send({ success: true, searchQueries });
 | 
				
			||||||
      .catch(error => {
 | 
					    })
 | 
				
			||||||
         res.status(404).send({ success: false, message: error.message });
 | 
					    .catch(error => {
 | 
				
			||||||
      });
 | 
					      res.status(404).send({ success: false, message: error.message });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = historyController;
 | 
					module.exports = historyController;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,4 +1,4 @@
 | 
				
			|||||||
const UserRepository = require('src/user/userRepository');
 | 
					const UserRepository = require("src/user/userRepository");
 | 
				
			||||||
const userRepository = new UserRepository();
 | 
					const userRepository = new UserRepository();
 | 
				
			||||||
/**
 | 
					/**
 | 
				
			||||||
 * Controller: Retrieves settings of a logged in user
 | 
					 * Controller: Retrieves settings of a logged in user
 | 
				
			||||||
@@ -7,36 +7,35 @@ const userRepository = new UserRepository();
 | 
				
			|||||||
 * @returns {Callback}
 | 
					 * @returns {Callback}
 | 
				
			||||||
 */
 | 
					 */
 | 
				
			||||||
const getSettingsController = (req, res) => {
 | 
					const getSettingsController = (req, res) => {
 | 
				
			||||||
   const user = req.loggedInUser;
 | 
					  const username = req.loggedInUser ? req.loggedInUser.username : null;
 | 
				
			||||||
   const username = user === undefined ? undefined : user.username;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   userRepository.getSettings(username)
 | 
					 | 
				
			||||||
      .then(settings => {
 | 
					 | 
				
			||||||
         res.send({ success: true, settings });
 | 
					 | 
				
			||||||
      })
 | 
					 | 
				
			||||||
      .catch(error => {
 | 
					 | 
				
			||||||
         res.status(404).send({ success: false, message: error.message });
 | 
					 | 
				
			||||||
      });
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  userRepository
 | 
				
			||||||
 | 
					    .getSettings(username)
 | 
				
			||||||
 | 
					    .then(settings => {
 | 
				
			||||||
 | 
					      res.send({ success: true, settings });
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch(error => {
 | 
				
			||||||
 | 
					      res.status(404).send({ success: false, message: error.message });
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const updateSettingsController = (req, res) => {
 | 
					const updateSettingsController = (req, res) => {
 | 
				
			||||||
   const user = req.loggedInUser;
 | 
					  const username = req.loggedInUser ? req.loggedInUser.username : null;
 | 
				
			||||||
   const username = user === undefined ? undefined : user.username;
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
   const idempotencyKey = req.headers('Idempotency-Key'); // TODO implement better transactions
 | 
					  const idempotencyKey = req.headers("Idempotency-Key"); // TODO implement better transactions
 | 
				
			||||||
   const { dark_mode, emoji } = req.body;
 | 
					  const { dark_mode, emoji } = req.body;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   userRepository.updateSettings(username, dark_mode, emoji)
 | 
					  userRepository
 | 
				
			||||||
      .then(settings => {
 | 
					    .updateSettings(username, dark_mode, emoji)
 | 
				
			||||||
         res.send({ success: true, settings });
 | 
					    .then(settings => {
 | 
				
			||||||
      })
 | 
					      res.send({ success: true, settings });
 | 
				
			||||||
      .catch(error => {
 | 
					    })
 | 
				
			||||||
         res.status(404).send({ success: false, message: error.message });
 | 
					    .catch(error => {
 | 
				
			||||||
      });
 | 
					      res.status(404).send({ success: false, message: error.message });
 | 
				
			||||||
}
 | 
					    });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
   getSettingsController,
 | 
					  getSettingsController,
 | 
				
			||||||
   updateSettingsController
 | 
					  updateSettingsController
 | 
				
			||||||
}
 | 
					};
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,47 +1,52 @@
 | 
				
			|||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
const Tautulli = require('src/tautulli/tautulli');
 | 
					const Tautulli = require("src/tautulli/tautulli");
 | 
				
			||||||
const apiKey = configuration.get('tautulli', 'apiKey');
 | 
					const apiKey = configuration.get("tautulli", "apiKey");
 | 
				
			||||||
const ip = configuration.get('tautulli', 'ip');
 | 
					const ip = configuration.get("tautulli", "ip");
 | 
				
			||||||
const port = configuration.get('tautulli', 'port');
 | 
					const port = configuration.get("tautulli", "port");
 | 
				
			||||||
const tautulli = new Tautulli(apiKey, ip, port);
 | 
					const tautulli = new Tautulli(apiKey, ip, port);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function handleError(error, res) {
 | 
					function handleError(error, res) {
 | 
				
			||||||
  const { status, message } = error;
 | 
					  const { status, message } = error;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  if (status && message) {
 | 
					  if (status && message) {
 | 
				
			||||||
    res.status(status).send({ success: false, message })
 | 
					    return res.status(status).send({ success: false, message });
 | 
				
			||||||
  } else {
 | 
					  } else {
 | 
				
			||||||
    console.log('caught view history controller error', error)
 | 
					    console.log("caught view history controller error", error);
 | 
				
			||||||
    res.status(500).send({ message: 'An unexpected error occured while fetching view history'})
 | 
					    return res.status(500).send({
 | 
				
			||||||
 | 
					      message: "An unexpected error occured while fetching view history"
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function watchTimeStatsController(req, res) {
 | 
					function watchTimeStatsController(req, res) {
 | 
				
			||||||
  const user = req.loggedInUser;
 | 
					  const user = req.loggedInUser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  tautulli.watchTimeStats(user.plex_userid)
 | 
					  return tautulli
 | 
				
			||||||
 | 
					    .watchTimeStats(user.plex_userid)
 | 
				
			||||||
    .then(data => {
 | 
					    .then(data => {
 | 
				
			||||||
      console.log('data', data, JSON.stringify(data.response.data))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      return res.send({
 | 
					      return res.send({
 | 
				
			||||||
        success: true,
 | 
					        success: true,
 | 
				
			||||||
        data: data.response.data,
 | 
					        data: data.response.data,
 | 
				
			||||||
        message: 'watch time successfully fetched from tautulli'
 | 
					        message: "watch time successfully fetched from tautulli"
 | 
				
			||||||
      })
 | 
					      });
 | 
				
			||||||
    })
 | 
					    })
 | 
				
			||||||
 | 
					    .catch(error => handleError(error, res));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getPlaysByDayOfWeekController(req, res) {
 | 
					function getPlaysByDayOfWeekController(req, res) {
 | 
				
			||||||
  const user = req.loggedInUser;
 | 
					  const user = req.loggedInUser;
 | 
				
			||||||
  const { days, y_axis } = req.query;
 | 
					  const { days, y_axis } = req.query;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  tautulli.getPlaysByDayOfWeek(user.plex_userid, days, y_axis)
 | 
					  return tautulli
 | 
				
			||||||
    .then(data => res.send({
 | 
					    .getPlaysByDayOfWeek(user.plex_userid, days, y_axis)
 | 
				
			||||||
      success: true,
 | 
					    .then(data =>
 | 
				
			||||||
      data: data.response.data,
 | 
					      res.send({
 | 
				
			||||||
      message: 'play by day of week successfully fetched from tautulli'
 | 
					        success: true,
 | 
				
			||||||
    })
 | 
					        data: data.response.data,
 | 
				
			||||||
 | 
					        message: "play by day of week successfully fetched from tautulli"
 | 
				
			||||||
 | 
					      })
 | 
				
			||||||
    )
 | 
					    )
 | 
				
			||||||
 | 
					    .catch(error => handleError(error, res));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
function getPlaysByDaysController(req, res) {
 | 
					function getPlaysByDaysController(req, res) {
 | 
				
			||||||
@@ -52,49 +57,46 @@ function getPlaysByDaysController(req, res) {
 | 
				
			|||||||
    return res.status(422).send({
 | 
					    return res.status(422).send({
 | 
				
			||||||
      success: false,
 | 
					      success: false,
 | 
				
			||||||
      message: "Missing parameter: days (number)"
 | 
					      message: "Missing parameter: days (number)"
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  const allowedYAxisDataType = ['plays', 'duration'];
 | 
					  const allowedYAxisDataType = ["plays", "duration"];
 | 
				
			||||||
  if (!allowedYAxisDataType.includes(y_axis)) {
 | 
					  if (!allowedYAxisDataType.includes(y_axis)) {
 | 
				
			||||||
    return res.status(422).send({
 | 
					    return res.status(422).send({
 | 
				
			||||||
      success: false,
 | 
					      success: false,
 | 
				
			||||||
      message: `Y axis parameter must be one of values: [${ allowedYAxisDataType }]`
 | 
					      message: `Y axis parameter must be one of values: [${allowedYAxisDataType}]`
 | 
				
			||||||
    })
 | 
					    });
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  tautulli.getPlaysByDays(user.plex_userid, days, y_axis)
 | 
					  return tautulli
 | 
				
			||||||
    .then(data => res.send({
 | 
					    .getPlaysByDays(user.plex_userid, days, y_axis)
 | 
				
			||||||
 | 
					    .then(data =>
 | 
				
			||||||
 | 
					      res.send({
 | 
				
			||||||
        success: true,
 | 
					        success: true,
 | 
				
			||||||
        data: data.response.data
 | 
					        data: data.response.data
 | 
				
			||||||
      }))
 | 
					      })
 | 
				
			||||||
 | 
					    )
 | 
				
			||||||
 | 
					    .catch(error => handleError(error, res));
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					 | 
				
			||||||
function userViewHistoryController(req, res) {
 | 
					function userViewHistoryController(req, res) {
 | 
				
			||||||
   const user = req.loggedInUser;
 | 
					  const user = req.loggedInUser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   console.log('user', user)
 | 
					  // TODO here we should check if we can init tau
 | 
				
			||||||
 | 
					  // and then return 501 Not implemented
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  return tautulli
 | 
				
			||||||
 | 
					    .viewHistory(user.plex_userid)
 | 
				
			||||||
 | 
					    .then(data => {
 | 
				
			||||||
 | 
					      return res.send({
 | 
				
			||||||
 | 
					        success: true,
 | 
				
			||||||
 | 
					        data: data.response.data.data,
 | 
				
			||||||
 | 
					        message: "view history successfully fetched from tautulli"
 | 
				
			||||||
 | 
					      });
 | 
				
			||||||
 | 
					    })
 | 
				
			||||||
 | 
					    .catch(error => handleError(error, res));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
   // TODO here we should check if we can init tau
 | 
					  // const username = user.username;
 | 
				
			||||||
   // and then return 501 Not implemented
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   tautulli.viewHistory(user.plex_userid)
 | 
					 | 
				
			||||||
     .then(data => {
 | 
					 | 
				
			||||||
       console.log('data', data, JSON.stringify(data.response.data.data))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
       return res.send({
 | 
					 | 
				
			||||||
         success: true,
 | 
					 | 
				
			||||||
         data: data.response.data.data,
 | 
					 | 
				
			||||||
         message: 'view history successfully fetched from tautulli'
 | 
					 | 
				
			||||||
       })
 | 
					 | 
				
			||||||
     })
 | 
					 | 
				
			||||||
     .catch(error => handleError(error))
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
   // const username = user.username;
 | 
					 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = {
 | 
					module.exports = {
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,11 +1,11 @@
 | 
				
			|||||||
const mustBeAuthenticated = (req, res, next) => {
 | 
					const mustBeAuthenticated = (req, res, next) => {
 | 
				
			||||||
   if (req.loggedInUser === undefined) {
 | 
					  if (req.loggedInUser === undefined) {
 | 
				
			||||||
      return res.status(401).send({
 | 
					    return res.status(401).send({
 | 
				
			||||||
         success: false,
 | 
					      success: false,
 | 
				
			||||||
         message: 'You must be logged in.',
 | 
					      message: "You must be logged in."
 | 
				
			||||||
      });
 | 
					    });
 | 
				
			||||||
   }
 | 
					  }
 | 
				
			||||||
   return next();
 | 
					  return next();
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module.exports = mustBeAuthenticated;
 | 
					module.exports = mustBeAuthenticated;
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										32
									
								
								seasoned_api/src/webserver/middleware/reqTokenToUser.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								seasoned_api/src/webserver/middleware/reqTokenToUser.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,32 @@
 | 
				
			|||||||
 | 
					/* eslint-disable no-param-reassign */
 | 
				
			||||||
 | 
					const configuration = require("src/config/configuration").getInstance();
 | 
				
			||||||
 | 
					const Token = require("src/user/token");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const secret = configuration.get("authentication", "secret");
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Token example:
 | 
				
			||||||
 | 
					// curl -i -H "Authorization:[token]" localhost:31459/api/v1/user/history
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const reqTokenToUser = (req, res, next) => {
 | 
				
			||||||
 | 
					  const cookieAuthToken = req.cookies.authorization;
 | 
				
			||||||
 | 
					  const headerAuthToken = req.headers.authorization;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  if (cookieAuthToken || headerAuthToken) {
 | 
				
			||||||
 | 
					    try {
 | 
				
			||||||
 | 
					      const token = Token.fromString(
 | 
				
			||||||
 | 
					        cookieAuthToken || headerAuthToken,
 | 
				
			||||||
 | 
					        secret
 | 
				
			||||||
 | 
					      );
 | 
				
			||||||
 | 
					      req.loggedInUser = token.user;
 | 
				
			||||||
 | 
					    } catch (error) {
 | 
				
			||||||
 | 
					      req.loggedInUser = undefined;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  } else {
 | 
				
			||||||
 | 
					    // guest session
 | 
				
			||||||
 | 
					    console.debug("No auth token in header or cookie.");
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  next();
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = reqTokenToUser;
 | 
				
			||||||
@@ -1,23 +0,0 @@
 | 
				
			|||||||
/* eslint-disable no-param-reassign */
 | 
					 | 
				
			||||||
const configuration = require('src/config/configuration').getInstance();
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const secret = configuration.get('authentication', 'secret');
 | 
					 | 
				
			||||||
const Token = require('src/user/token');
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
// Token example:
 | 
					 | 
				
			||||||
// curl -i -H "Authorization:[token]" localhost:31459/api/v1/user/history
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
const tokenToUser = (req, res, next) => {
 | 
					 | 
				
			||||||
  const rawToken = req.headers.authorization;
 | 
					 | 
				
			||||||
  if (rawToken) {
 | 
					 | 
				
			||||||
    try {
 | 
					 | 
				
			||||||
      const token = Token.fromString(rawToken, secret);
 | 
					 | 
				
			||||||
      req.loggedInUser = token.user;
 | 
					 | 
				
			||||||
    } catch (error) {
 | 
					 | 
				
			||||||
      req.loggedInUser = undefined;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					 | 
				
			||||||
  next();
 | 
					 | 
				
			||||||
};
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
module.exports = tokenToUser;
 | 
					 | 
				
			||||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user