var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
    function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
    return new (P || (P = Promise))(function (resolve, reject) {
        function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
        function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
        function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
        step((generator = generator.apply(thisArg, _arguments || [])).next());
    });
};
import atob from 'atob';
import express from 'express';
import router from './imports/router/index.js';
import generateJwtServer from './imports/router/jwt.js';
import generateGuestServer from './imports/router/guest.js';
import generatePackagerServer from './imports/router/packager.js';
import axios from 'axios';
import http from 'http';
import { createProxyMiddleware, fixRequestBody, responseInterceptor } from 'http-proxy-middleware';
import { createRequire } from "module";
const require = createRequire(import.meta.url);
const expressPlayground = require('graphql-playground-middleware-express').default;
import moesif from 'moesif-nodejs';
import Debug from 'debug';
import waitOn from 'wait-on';
import { generateApolloClient } from '@deep-foundation/hasura/client.js';
import { DeepClient } from './imports/client.js';
import gql from 'graphql-tag';
import { containerController, DOCKER, getJwt } from './imports/router/links.js';
import _ from 'lodash';
import Cors from 'cors';
import cookieParser from 'cookie-parser';
import { serializeError } from 'serialize-error';
const DEEPLINKS_HASURA_PATH = process.env.DEEPLINKS_HASURA_PATH || 'localhost:8080';
const DEEPLINKS_HASURA_STORAGE_URL = process.env.DEEPLINKS_HASURA_STORAGE_URL || 'http://localhost:8000';
const DEEPLINKS_HASURA_SSL = process.env.DEEPLINKS_HASURA_SSL || 0;
const DEEPLINKS_HASURA_SECRET = process.env.DEEPLINKS_HASURA_SECRET || 'myadminsecretkey';
const MOESIF_TOKEN = process.env.MOESIF_TOKEN || '';
const DEEPLINKS_PUBLIC_URL = process.env.DEEPLINKS_PUBLIC_URL || '';
const PORT = process.env.PORT || 3006;
const debug = Debug('deeplinks');
const log = debug.extend('log');
const error = debug.extend('error');
export const delay = (time) => new Promise(res => setTimeout(() => res(null), time));
const makeDeepClient = (token) => {
    return new DeepClient({
        apolloClient: generateApolloClient({
            path: `${process.env.DEEPLINKS_HASURA_PATH}/v1/graphql`,
            ssl: !!+process.env.DEEPLINKS_HASURA_SSL,
            token
        }),
    });
};
const app = express();
app.use(cookieParser());
const httpServer = http.createServer(app);
const cors = Cors({ origin: '*' });
app.use(cors);
app.get('/gql', expressPlayground({
    tabs: [{
            endpoint: `${DEEPLINKS_PUBLIC_URL}/gql`,
            query: `query MyQuery {
      links(limit: 1) {
        id
      }
    }`,
            headers: {
                Authorization: 'Bearer TOKEN',
            },
        }],
}));
app.use('/gql', createProxyMiddleware((pathname, req) => {
    return !!pathname.match(`^/gql`);
}, {
    target: `http${DEEPLINKS_HASURA_SSL === '1' ? 's' : ''}://${DEEPLINKS_HASURA_PATH}`,
    changeOrigin: true,
    ws: true,
    logLevel: 'debug',
    pathRewrite: {
        "/gql": "/v1/graphql",
    },
}));
app.get(['/file'], createProxyMiddleware((pathname, req) => {
    return !!pathname.match(`^/file`);
}, {
    target: DEEPLINKS_HASURA_STORAGE_URL,
    changeOrigin: true,
    logLevel: 'debug',
    pathRewrite: (path, req) => __awaiter(void 0, void 0, void 0, function* () {
        var _a, _b, _c, _d;
        console.log('/file get proxy', 'path', path);
        console.log('/file get proxy', 'req.baseUrl', req.baseUrl);
        console.log('/file get proxy', 'req.originalUrl', req.originalUrl);
        console.log('/file get proxy', 'req.protocol', req.protocol);
        console.log('/file get proxy', 'req.hostname', req.hostname);
        console.log('/file get proxy', 'req.url', req.url);
        console.log('/file get proxy', 'req.query.linkId', req.query.linkId);
        req.params;
        const headers = req.headers;
        console.log('/file get proxy', 'headers', headers);
        const cookies = req.cookies;
        console.log('/file get proxy', 'cookies', JSON.stringify(cookies, null, 2));
        let token = '';
        let authorizationHeader = headers['authorization'];
        if (authorizationHeader) {
            token = authorizationHeader.split(' ')[1];
            console.log('/file get proxy', 'header token', token);
        }
        else {
            const tokenCookie = cookies === null || cookies === void 0 ? void 0 : cookies['dc-dg-token'];
            if (tokenCookie) {
                token = (_a = JSON.parse(tokenCookie)) === null || _a === void 0 ? void 0 : _a.value;
                console.log('/file get proxy', 'cookie token', token);
                if (token) {
                    req.headers.authorization = `Bearer ${token}`;
                }
                console.log('/file get proxy', 'cookie token is set as header token');
            }
        }
        console.log('/file get proxy', 'result token', token);
        const deep = makeDeepClient(token);
        const linkId = req.query.linkId;
        const result = yield deep.apolloClient.query({
            query: gql `{
        files(where: {link_id: {_eq: ${linkId}}}) {
          id
        }
      }`
        });
        const fileId = (_d = (_c = (_b = result === null || result === void 0 ? void 0 : result.data) === null || _b === void 0 ? void 0 : _b.files) === null || _c === void 0 ? void 0 : _c[0]) === null || _d === void 0 ? void 0 : _d.id;
        console.log('/file get proxy', 'fileId', fileId);
        if (fileId) {
            return `/v1/files/${fileId}`;
        }
        else {
            return `/v1/files/00000000-0000-0000-0000-000000000000`;
        }
    })
}));
app.post('/file', (req, res, next) => __awaiter(void 0, void 0, void 0, function* () {
    console.log('/file post proxy', 'DEEPLINKS_HASURA_STORAGE_URL', DEEPLINKS_HASURA_STORAGE_URL);
    const headers = req.headers;
    console.log('/file post proxy', 'headers', JSON.stringify(headers, null, 2));
    const cookies = req.cookies;
    console.log('/file post proxy', 'cookies', JSON.stringify(cookies, null, 2));
    let userId;
    let linkId;
    try {
        const claims = atob(`${headers['authorization'] ? headers['authorization'] : headers['Authorization']}`.split(' ')[1].split('.')[1]);
        userId = +(JSON.parse(claims)['https://hasura.io/jwt/claims']['x-hasura-user-id']);
        linkId = +(headers['linkId'] || headers['linkid']);
        console.log('/file post proxy', 'linkId', linkId);
    }
    catch (e) {
        const serializedError = serializeError(e);
        console.log('/file post proxy', 'error: ', JSON.stringify(serializedError, null, 2));
    }
    if (!userId)
        res.status(403).send('Update CAN NOT be processes');
    const canResult = (yield deep.can(linkId, userId, deep.idLocal('@deep-foundation/core', 'AllowUpdateType'))) || (yield deep.can(null, userId, deep.idLocal('@deep-foundation/core', 'AllowAdmin')));
    console.log('/file post proxy', 'can', yield deep.can(linkId, userId, deep.idLocal('@deep-foundation/core', 'AllowUpdateType')), 'isAdmin', yield deep.can(null, userId, deep.idLocal('@deep-foundation/core', 'AllowAdmin')));
    console.log('/file post proxy', 'userId', userId, typeof (userId));
    console.log('/file post proxy', 'canResult', canResult);
    if (!canResult)
        return res.status(403).send(`You cant update link ##${linkId} as user ##${userId}, and user ##${userId} is not admin.`);
    yield createProxyMiddleware((pathname, req) => {
        return !!pathname.match(`^/file`);
    }, {
        target: DEEPLINKS_HASURA_STORAGE_URL,
        selfHandleResponse: true,
        logLevel: 'debug',
        onError: (err, req, res, target) => {
            console.log('/file post proxy', 'onError', err);
            res.writeHead(500, {
                'Content-Type': 'text/plain',
            });
            res.end('Something went wrong. And we are reporting a custom error message.');
        },
        onProxyRes: responseInterceptor((responseBuffer, proxyRes, req, res) => __awaiter(void 0, void 0, void 0, function* () {
            var _e, _f, _g;
            console.log('/file post proxy', 'onProxyRes');
            const response = responseBuffer.toString('utf8');
            console.log('/file post proxy', `RESPONSE ${response}`);
            let files;
            try {
                files = JSON.parse(response);
                console.log('/file post proxy', 'files', files);
                if (!files)
                    return response;
                const UPDATE_FILE_LINKID = gql `mutation UPDATE_FILE_LINKID($linkId: bigint, $fileid: uuid, $uploadedByLinkId: bigint) {
          updateFiles(where: {id: {_eq: $fileid}}, _set: {link_id: $linkId, uploadedByLinkId: $uploadedByLinkId }){
            returning {
              id
              link_id
              uploadedByLinkId
            }
          }
        }`;
                console.log('/file post proxy', 'files[0].id', files.id);
                const updated = yield client.mutate({
                    mutation: UPDATE_FILE_LINKID,
                    variables: {
                        fileid: files.id,
                        linkId: linkId,
                        uploadedByLinkId: userId
                    },
                });
                console.log('/file post proxy', 'linkid', linkId);
                console.log('/file post proxy', 'data', (_f = (_e = updated === null || updated === void 0 ? void 0 : updated.data) === null || _e === void 0 ? void 0 : _e.updateFiles) === null || _f === void 0 ? void 0 : _f.returning);
            }
            catch (e) {
                const serializedError = serializeError(e);
                console.log('/file post proxy', 'try error: ', JSON.stringify(serializedError, null, 2));
                if ((_g = files[0]) === null || _g === void 0 ? void 0 : _g.id) {
                    yield client.mutate({
                        mutation: gql `mutation DELETE_FILE($fileid: uuid) { deleteFiles(where: {id: {_eq: $fileid}}){ returning { id } } }`,
                        variables: {
                            fileid: files.id,
                        },
                    });
                    return JSON.stringify({ error: 'one link - one file' });
                }
            }
            return response;
        })),
        changeOrigin: true,
        pathRewrite: {
            "/file": "/v1/files",
        },
    })(req, res, next);
}));
app.use(['/v1', '/v1alpha1', '/v2', '/console'], createProxyMiddleware({
    target: `http${DEEPLINKS_HASURA_SSL === '1' ? 's' : ''}://${DEEPLINKS_HASURA_PATH}`,
    changeOrigin: true,
    ws: true,
    logLevel: 'debug',
}));
if (MOESIF_TOKEN) {
    const moesifMiddleware = moesif({ applicationId: MOESIF_TOKEN });
    app.use(moesifMiddleware);
    moesifMiddleware.startCaptureOutgoing();
}
app.use(express.json());
app.use('/', router);
const start = () => __awaiter(void 0, void 0, void 0, function* () {
    const jwtServer = generateJwtServer(httpServer);
    const guestServer = generateGuestServer(httpServer);
    const packagerServer = generatePackagerServer(httpServer);
    yield jwtServer.start();
    yield guestServer.start();
    yield packagerServer.start();
    jwtServer.applyMiddleware({ path: '/api/jwt', app });
    guestServer.applyMiddleware({ path: '/api/guest', app });
    packagerServer.applyMiddleware({ path: '/api/packager', app });
    yield new Promise(resolve => httpServer.listen({ port: process.env.PORT }, resolve));
    log(`Hello bugfixers! Listening ${process.env.PORT} port`);
    try {
        yield waitOn({ resources: [`http${DEEPLINKS_HASURA_SSL === '1' ? 's' : ''}-get://${DEEPLINKS_HASURA_PATH}/console`] });
        yield axios({
            method: 'post',
            url: `http${DEEPLINKS_HASURA_SSL === '1' ? 's' : ''}://${DEEPLINKS_HASURA_PATH}/v1/metadata`,
            headers: { 'x-hasura-admin-secret': DEEPLINKS_HASURA_SECRET, 'Content-Type': 'application/json' },
            data: { type: 'reload_metadata', args: {} }
        }).then(() => {
            log('hasura metadata reloaded');
        }, () => {
            error('hasura metadata broken');
        });
    }
    catch (e) {
        const serializedError = serializeError(e);
        error(JSON.stringify(serializedError, null, 2));
    }
});
start();
const client = generateApolloClient({
    path: `${process.env.DEEPLINKS_HASURA_PATH}/v1/graphql`,
    ssl: !!+process.env.DEEPLINKS_HASURA_SSL,
    secret: process.env.DEEPLINKS_HASURA_SECRET,
});
const deep = new DeepClient({
    apolloClient: client,
});
const routesDebug = Debug('deeplinks').extend('eh').extend('routes');
const routesDebugLog = routesDebug.extend('log');
const routesDebugError = routesDebug.extend('error');
let currentServers = {};
let currentPorts = {};
let busy = false;
let portTypeId = 0;
const toJSON = (data) => JSON.stringify(data, Object.getOwnPropertyNames(data), 2);
let mainPort;
const handleRoutes = () => __awaiter(void 0, void 0, void 0, function* () {
    var _h, _j, _k, _l, _m, _o, _p, _q, _r, _s;
    if (busy)
        return;
    busy = true;
    try {
        const portTypeId = deep.idLocal('@deep-foundation/core', 'Port');
        const handleRouteTypeId = deep.idLocal('@deep-foundation/core', 'HandleRoute');
        const routerStringUseTypeId = deep.idLocal('@deep-foundation/core', 'RouterStringUse');
        const routerListeningTypeId = deep.idLocal('@deep-foundation/core', 'RouterListening');
        try {
            if (!mainPort)
                mainPort = yield deep.id('@deep-foundation/main-port', 'port');
        }
        catch (error) { }
        const routesResult = yield client.query({
            query: gql `
        query {
          ports: links(where: {
            type_id: { _eq: "${portTypeId}" }
          }) {
            id
            port: value
            routerListening: in(where: {
              type_id: { _eq: "${routerListeningTypeId}" }
            }) {
              id
              router: from {
                id
                routerStringUse: in(where: {
                  type_id: { _eq: "${routerStringUseTypeId}" }
                }) {
                  id
                  routeString: value
                  route: from {
                    id
                    handleRoute: out(where: {
                      type_id: { _eq: "${handleRouteTypeId}" }
                    }) {
                      id
                      handler: to {
                        id
                        supports: from {
                          id
                          isolation: from {
                            id
                            image: value
                          }
                        }
                        file: to {
                          id
                          code: value
                        }
                      }
                    }
                  }
                }
              }
            }
          }
        }
      `, variables: {}
        });
        const portsResult = routesResult.data.ports;
        routesDebugLog('portsResult', JSON.stringify(portsResult, null, 2));
        const ports = {};
        for (const port of portsResult) {
            const portValue = (_h = port === null || port === void 0 ? void 0 : port.port) === null || _h === void 0 ? void 0 : _h.value;
            ports[portValue] = port;
        }
        routesDebugLog('ports', JSON.stringify(ports, null, 2));
        const updatedOrAddedPorts = [];
        for (const key in currentPorts) {
            if (currentPorts.hasOwnProperty(key)) {
                if (ports.hasOwnProperty(key)) {
                    if (!_.isEqual(currentPorts[key], ports[key])) {
                        currentPorts[key] = ports[key];
                        updatedOrAddedPorts.push(ports[key]);
                    }
                    else {
                    }
                }
                else {
                    if (currentServers.hasOwnProperty(key)) {
                        const element = currentServers[key];
                        element.close();
                        delete currentServers[key];
                    }
                    delete currentPorts[key];
                }
            }
        }
        for (const key in ports) {
            if (ports.hasOwnProperty(key)) {
                if (!currentPorts.hasOwnProperty(key)) {
                    currentPorts[key] = ports[key];
                    updatedOrAddedPorts.push(ports[key]);
                }
            }
        }
        routesDebugLog('updatedOrAddedPorts', JSON.stringify(updatedOrAddedPorts, null, 2));
        routesDebugLog('currentPorts', JSON.stringify(currentPorts, null, 2));
        const imageContainers = {};
        updatedOrAddedPorts.forEach(port => {
            port.routerListening.forEach(routerListening => {
                var _a;
                (_a = routerListening === null || routerListening === void 0 ? void 0 : routerListening.router) === null || _a === void 0 ? void 0 : _a.routerStringUse.forEach(routerStringUse => {
                    var _a;
                    (_a = routerStringUse === null || routerStringUse === void 0 ? void 0 : routerStringUse.route) === null || _a === void 0 ? void 0 : _a.handleRoute.forEach(handleRoute => {
                        var _a, _b, _c, _d;
                        imageContainers[(_d = (_c = (_b = (_a = handleRoute === null || handleRoute === void 0 ? void 0 : handleRoute.handler) === null || _a === void 0 ? void 0 : _a.supports) === null || _b === void 0 ? void 0 : _b.isolation) === null || _c === void 0 ? void 0 : _c.image) === null || _d === void 0 ? void 0 : _d.value] = {};
                    });
                });
            });
        });
        const imageList = Object.keys(imageContainers);
        routesDebugLog('imageList', imageList);
        for (const image of imageList) {
            routesDebugLog(`preparing container ${image}`);
            imageContainers[image] = yield containerController.newContainer({
                handler: image,
                forceRestart: true,
                publish: +DOCKER ? false : true,
            });
        }
        for (const port of updatedOrAddedPorts) {
            const portValue = ((_j = port === null || port === void 0 ? void 0 : port.port) === null || _j === void 0 ? void 0 : _j.value) || PORT;
            if (currentServers.hasOwnProperty(portValue)) {
                currentServers[portValue].close();
            }
            if (port.routerListening.length > 0) {
                routesDebugLog(`listening on port ${portValue}`);
                let portServer;
                if (+portValue === +PORT || (port === null || port === void 0 ? void 0 : port.id) === mainPort) {
                    portServer = nestedApp;
                }
                else {
                    portServer = express();
                    currentServers[portValue] = http.createServer({ maxHeaderSize: 10 * 1024 * 1024 * 1024 }, portServer).listen(portValue);
                }
                for (const routerListening of port.routerListening) {
                    const router = routerListening === null || routerListening === void 0 ? void 0 : routerListening.router;
                    for (const routerStringUse of router.routerStringUse) {
                        const routeString = (_k = routerStringUse === null || routerStringUse === void 0 ? void 0 : routerStringUse.routeString) === null || _k === void 0 ? void 0 : _k.value;
                        routesDebugLog(`route string ${routeString}`);
                        const route = routerStringUse === null || routerStringUse === void 0 ? void 0 : routerStringUse.route;
                        for (const handleRoute of route.handleRoute) {
                            const handler = handleRoute === null || handleRoute === void 0 ? void 0 : handleRoute.handler;
                            const handlerId = handler === null || handler === void 0 ? void 0 : handler.id;
                            routesDebugLog(`handler id ${handlerId}`);
                            const { token: jwt } = yield getJwt(handlerId, routesDebugLog);
                            routesDebugLog(`jwt ${jwt}`);
                            const image = (_o = (_m = (_l = handler === null || handler === void 0 ? void 0 : handler.supports) === null || _l === void 0 ? void 0 : _l.isolation) === null || _m === void 0 ? void 0 : _m.image) === null || _o === void 0 ? void 0 : _o.value;
                            routesDebugLog(`image`, image);
                            const container = imageContainers[image];
                            routesDebugLog(`container`, JSON.stringify(container, null, 2));
                            const { data: [_handler] = [] } = yield deep.select({ handler_id: handlerId }, { table: 'handlers', returning: 'dist { value }' });
                            const code = ((_q = (_p = _handler === null || _handler === void 0 ? void 0 : _handler.dist) === null || _p === void 0 ? void 0 : _p.value) === null || _q === void 0 ? void 0 : _q.value) || ((_s = (_r = handler === null || handler === void 0 ? void 0 : handler.file) === null || _r === void 0 ? void 0 : _r.code) === null || _s === void 0 ? void 0 : _s.value);
                            routesDebugLog(`code ${code}`);
                            routesDebugLog('container', container);
                            const filter = function (pathname, req) {
                                routesDebugLog('pathname', pathname, req.method, req.originalUrl, !!pathname.match(`^${routeString}`));
                                return !!pathname.match(`^${routeString}`);
                            };
                            const proxy = createProxyMiddleware(filter, {
                                target: `http://${container.host}:${container.port}`,
                                changeOrigin: true,
                                ws: true,
                                pathRewrite: {
                                    [routeString]: "/http-call",
                                },
                                logLevel: 'debug',
                                onProxyReq: (proxyReq, req, res) => {
                                    routesDebugLog('onProxyReq', req.baseUrl);
                                    routesDebugLog('deeplinks request');
                                    routesDebugLog('req.method', req.method);
                                    routesDebugLog('req.body', req.body);
                                    proxyReq.setHeader('deep-call-options', encodeURI(JSON.stringify({
                                        jwt,
                                        code,
                                        data: { routeString, path: req.path, originalUrl: req.originalUrl, baseUrl: req.baseUrl, handlerId, routeId: route.id, router: router.id },
                                    })));
                                    return fixRequestBody(proxyReq, req);
                                },
                                onProxyRes: (proxyRes, req, res) => {
                                    routesDebugLog('onProxyRes', req.baseUrl);
                                    proxyRes.on('data', function (data) {
                                        return __awaiter(this, void 0, void 0, function* () {
                                            try {
                                                data = data.toString('utf-8');
                                                routesDebugLog('data', data);
                                                if (data.startsWith('{')) {
                                                    data = JSON.parse(data);
                                                    if (data.hasOwnProperty('rejected')) {
                                                        routesDebugLog('rejected', data.rejected);
                                                        const handlingErrorTypeId = deep.idLocal('@deep-foundation/core', 'HandlingError');
                                                        routesDebugLog('handlingErrorTypeId', handlingErrorTypeId);
                                                    }
                                                }
                                            }
                                            catch (e) {
                                                const serializedError = serializeError(e);
                                                routesDebugError('deeplinks response error', JSON.stringify(serializedError, null, 2));
                                            }
                                        });
                                    });
                                }
                            });
                            portServer.use(routeString, proxy);
                        }
                    }
                }
            }
        }
    }
    catch (e) {
        const serializedError = serializeError(e);
        routesDebugLog(JSON.stringify(serializedError, null, 2));
    }
    busy = false;
});
let nestedApp = express.Router();
app.use(nestedApp);
const startRouteHandling = () => __awaiter(void 0, void 0, void 0, function* () {
    setInterval(handleRoutes, 5000);
});
startRouteHandling();
//# sourceMappingURL=index.js.map