Skip to content

Commit

Permalink
update: use app context
Browse files Browse the repository at this point in the history
  • Loading branch information
hexdecimal16 committed Aug 22, 2024
1 parent daa42bf commit faa8460
Show file tree
Hide file tree
Showing 8 changed files with 127 additions and 92 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
"hpagent": "^1.2.0",
"openai": "^4.0.0",
"pg": "^8.12.0",
"pg-pool": "^3.6.2",
"puppeteer": "^23.1.0",
"redis": "^4.7.0",
"sharp": "^0.33.5",
Expand Down
25 changes: 10 additions & 15 deletions src/cache/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,21 @@ import { RedisClientType, createClient } from 'redis';

let cacheClient: RedisClientType | null = null;

export async function getCacheClient(): Promise<RedisClientType | null> {
// If the client is already created, return it
if (cacheClient) return cacheClient;

if (!process.env.REDIS_URL) {
console.warn('Redis URL is not defined. Cache is disabled.');
return null;
export function getCacheClient(): RedisClientType | null {
if (!cacheClient && process.env.REDIS_URL) {
console.log('Initializing Redis Cache Client', process.env.REDIS_URL);
cacheClient = createClient({ url: process.env.REDIS_URL });
cacheClient.on('error', (err) => console.error('Redis Client Error', err));
cacheClient.on('connect', () => console.log('Connected to Redis'));
cacheClient.connect().catch((err) => {
console.error('Failed to connect to Redis:', err);
cacheClient = null; // Avoid using an invalid client
});
}

console.log('Creating Redis Cache Client', process.env.REDIS_URL);
if (cacheClient) return cacheClient;

cacheClient = createClient({ url: process.env.REDIS_URL });
cacheClient.on('error', (err) => console.error('Redis Client Error', err));

await cacheClient.connect();
return cacheClient;
}


export async function closeCacheClient(): Promise<void> {
if (cacheClient) {
await cacheClient.quit();
Expand Down
29 changes: 29 additions & 0 deletions src/context/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// context.ts
import { RedisClientType } from 'redis';
import { Pool, Client as PgClient } from 'pg';
import { getCacheClient, closeCacheClient } from '../cache';
import { getDBClient, closeDBClient } from '../repository';

export interface AppContext {
cacheClient: RedisClientType | null;
dbClient: Pool | PgClient | null;
}

let context: AppContext = {
cacheClient: null,
dbClient: null,
};

export async function initializeContext(): Promise<void> {
context.cacheClient = await getCacheClient();
context.dbClient = await getDBClient();
}

export function getContext(): AppContext {
return context;
}

export async function closeContext(): Promise<void> {
await closeCacheClient();
await closeDBClient();
}
12 changes: 3 additions & 9 deletions src/handlers/catalogHandler.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import axios from 'axios';
import { getRatingsfromDB, scrapeRatings } from '../utils/ratingScrapers';
import { CINEMETA_BASE_URL, CINEMETA_CATALOG_URL } from '../constants/urls';
import { closeCacheClient, getCacheClient } from '../cache';
import { DEFAULT_PROVIDERS } from '../constants/costants';
import { isDatabaseConnected } from '../repository';
import { getContext } from '../context';

async function fetchCatalog(url: string, providers: string[]): Promise<any> {
const response = await axios.get(url);
Expand Down Expand Up @@ -38,17 +38,13 @@ export async function bestYearByYearCatalog(type: string, extra: any, providers:
return fetchCatalog(url, providers);
}

export async function handleCatalogRequest({ id, type, extra, config }: any) {
export async function handleCatalogRequest({ id, type, extra, config }: any): Promise<any> {
let providers = DEFAULT_PROVIDERS;
if (config && config.providers) {
providers = config.providers;
}
let cacheClient = await getCacheClient();
let cacheClient = await getContext().cacheClient;
const key = id + JSON.stringify(extra);
if (cacheClient != null && !cacheClient.isOpen) {
console.log("Cache is not open, opening it again");
cacheClient = await getCacheClient();
}
try {
// Check if the response is cached
const cachedResponse = await cacheClient?.get(key);
Expand Down Expand Up @@ -81,7 +77,5 @@ export async function handleCatalogRequest({ id, type, extra, config }: any) {
} catch (error) {
console.error("Error in CatalogHandler:", error);
return { metas: [] };
} finally {
closeCacheClient();
}
}
3 changes: 2 additions & 1 deletion src/handlers/metaHandler.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { DEFAULT_PROVIDERS } from '../constants/costants';
import { scrapeRatings } from '../utils/ratingScrapers';
import { MetaDetail } from 'stremio-addon-sdk';

export async function handleMetaRequest({ id, type, extra, config }: any) {
export async function handleMetaRequest({ id, type, extra, config }: any): Promise<MetaDetail> {
let providers = DEFAULT_PROVIDERS;
if (config && config.providers) {
providers = config.providers;
Expand Down
75 changes: 42 additions & 33 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,42 +3,51 @@ import { handleMetaRequest } from "./handlers/metaHandler";
import { handleCatalogRequest } from "./handlers/catalogHandler";
import manifest from "./manifest";
import dotenv from "dotenv";
import { closeClient, getClient } from "./repository";
import { get } from "http";
import { closeDBClient, getDBClient } from "./repository";
import { closeCacheClient, getCacheClient } from "./cache";
import pg from 'pg';
import { initializeContext } from "./context";

dotenv.config();

const builder = new addonBuilder(manifest);
initializeContext().then(() => {
const builder = new addonBuilder(manifest);

// Catalog Handlers
builder.defineCatalogHandler(async (args: Args) => {
console.log("CatalogHandler args:", args);
await getClient();
try {
return await handleCatalogRequest(args);
} catch (error) {
console.error("Error in CatalogHandler:", error);
return { metas: [] };
} finally{
console.log("CatalogHandler finally");
closeClient();
}
});
// Catalog Handlers
builder.defineCatalogHandler(async (args: Args) => {
console.log("CatalogHandler args:", args);
await getDBClient();
try {
return await handleCatalogRequest(args);
} catch (error) {
console.error("Error in CatalogHandler:", error);
return { metas: [] };
}
});

// Meta Handlers
builder.defineMetaHandler(async (args: { type: ContentType, id: string }) => {
await getClient();
try {
return { meta: await handleMetaRequest(args) };
} catch (error) {
console.error("Error in MetaHandler:", error);
return { meta: {} as any };
} finally {
closeClient();
}
});
// Meta Handlers
builder.defineMetaHandler(async (args: { type: ContentType, id: string }) => {
await getDBClient();
try {
return { meta: await handleMetaRequest(args) };
} catch (error) {
console.error("Error in MetaHandler:", error);
return { meta: {} as any };
}
});

// Additional handlers (stream, subtitle, etc.) can be added similarly
const port = Number(process.env.PORT) || 3000;
serveHTTP(builder.getInterface(), { port: port });
console.log(`🚀 Link for addon http://localhost:${port}`);
// Graceful shutdown
process.on('SIGINT', async () => {
await closeDBClient(); // Close database connection
await closeCacheClient(); // Close Redis client
process.exit(0);
});

// Additional handlers (stream, subtitle, etc.) can be added similarly
const port = Number(process.env.PORT) || 3000;
serveHTTP(builder.getInterface(), { port: port });
console.log(`🚀 Link for addon http://localhost:${port}`);
}).catch((error) => {
console.error('Failed to initialize context:', error);
process.exit(1);
});
69 changes: 37 additions & 32 deletions src/repository/index.ts
Original file line number Diff line number Diff line change
@@ -1,57 +1,62 @@
import pg from 'pg'
const { Client } = pg
import pg from 'pg';
const { Pool } = pg;

let client: pg.Client | null = null
let pool: pg.Pool | null = null;

;

export async function getClient(): Promise<pg.Client | null> {
const connectionStr = process.env.DATABASE_URL
export async function getDBClient(): Promise<pg.Pool | null> {
const connectionStr = process.env.DATABASE_URL;
if (!connectionStr) {
console.error('DATABASE_URL so not using database')
console.error('DATABASE_URL is not defined');
return null;
}
if (client) {
console.error('Already connected to database')
return client

if (pool) {
return pool;
}
client = new Client({ connectionString: connectionStr, ssl: { rejectUnauthorized: false } })
client.on('error', (error) => {
console.error('Database error:', error)
});
client.on('end', () => {
console.log('Database connection closed')

pool = new Pool({ connectionString: connectionStr, ssl: { rejectUnauthorized: false } });
pool.on('error', (error) => {
console.error('Database pool error:', error);
});
client.connect()

return pool;
}

export async function closeClient() {
if (client) {
await client.end()
client = null
export async function closeDBClient(): Promise<void> {
if (pool) {
try {
await pool.end();
} catch (error) {
console.error('Error closing database pool:', error);
} finally {
pool = null;
}
}
}

export async function getRatingsfromTTIDs(ttids: string[]): Promise<Record<string, Record<string, string>>> {
if (!client) {
throw new Error('Not connected to database')
const pool = await getDBClient();
if (!pool) {
throw new Error('Not connected to database');
}
try {
const query = `SELECT ttid, ratings.provider, rating FROM ratings WHERE ttid = ANY($1)`
const { rows } = await client.query(query, [ttids])
const client = await pool.connect();
const query = `SELECT ttid, ratings.provider, rating FROM ratings WHERE ttid = ANY($1)`;
const { rows } = await client.query(query, [ttids]);
client.release(); // Release client back to pool
return rows.reduce((acc: Record<string, Record<string, string>>, row: any) => {
if (!acc[row.ttid]) {
acc[row.ttid] = {}
acc[row.ttid] = {};
}
acc[row.ttid][row.provider] = row.rating
return acc
acc[row.ttid][row.provider] = row.rating;
return acc;
}, {});
} catch (error) {
console.error('Error fetching ratings from database:', error)
return {}
console.error('Error fetching ratings from database:', error);
return {};
}
}

export function isDatabaseConnected(): boolean {
return client != null
return pool != null;
}
5 changes: 3 additions & 2 deletions src/utils/ratingScrapers.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { MetaDetail } from 'stremio-addon-sdk';
import { getCacheClient } from '../cache';
import { fetchBingRatings, fetchGoogleRatings, fetchYahooRatings, getMetadata } from './api';
import { RedisClientType } from 'redis';
import { addRatingToImage } from './image';
import axios from 'axios';
import { getRatingsfromTTIDs } from '../repository';
import { getContext } from '../context';

export async function getRatingsFromGoogle(query: string, imdbId: string, cacheClient: RedisClientType | null): Promise<Record<string, string>> {
try {
Expand Down Expand Up @@ -73,7 +73,8 @@ export async function getRatingsFromYahoo(query: string, imdbId: string, cacheCl
}

export async function scrapeRatings(imdbId: string, type: string, providers: string[]): Promise<MetaDetail> {
const cacheClient = await getCacheClient();

const cacheClient = getContext().cacheClient;
const metadata = await getMetadata(imdbId, type);
let ratingMap: Record<string, string> = {};
try {
Expand Down

0 comments on commit faa8460

Please sign in to comment.