const fs = require("fs"); const path = require("path"); const express = require("express"); const { feast } = require("./protos"); const registryBuf = fs.readFileSync( path.resolve(__dirname, "../public/registry.db"), ); const parsedRegistry = feast.core.Registry.decode(registryBuf); const projectsList = JSON.parse( fs.readFileSync(path.resolve(__dirname, "../public/projects-list.json")), ); const toJSON = (obj) => (obj && obj.toJSON ? obj.toJSON() : obj); const withType = (type) => (fv) => ({ ...toJSON(fv), type, }); const state = { entities: (parsedRegistry.entities || []).map(toJSON), featureViews: (parsedRegistry.featureViews || []).map( withType("featureView"), ), onDemandFeatureViews: (parsedRegistry.onDemandFeatureViews || []).map( withType("onDemandFeatureView"), ), streamFeatureViews: (parsedRegistry.streamFeatureViews || []).map( withType("streamFeatureView"), ), featureServices: (parsedRegistry.featureServices || []).map(toJSON), dataSources: (parsedRegistry.dataSources || []).map(toJSON), savedDatasets: (parsedRegistry.savedDatasets || []).map(toJSON), projects: (parsedRegistry.projects || []).map(toJSON), }; const allFeatureViews = () => [ ...state.featureViews, ...state.onDemandFeatureViews, ...state.streamFeatureViews, ]; const objectProject = (obj) => obj?.spec?.project || obj?.project; const filterByProject = (items, project) => { if (!project || project === "all") return items; return items.filter((item) => objectProject(item) === project); }; const allFeatures = (project) => filterByProject(allFeatureViews(), project).flatMap((fv) => (fv?.spec?.features || []).map((feature) => ({ name: feature.name, featureViewName: fv.spec?.name, valueType: feature.valueType, project: fv.spec?.project, })), ); const responseList = (res, key, items) => { res.json({ [key]: items, pagination: {}, relationships: {}, }); }; const findByName = (items, name) => items.find((item) => item?.spec?.name === name || item?.name === name); const entityPayloadToResource = (payload) => ({ spec: { name: payload.name, joinKey: payload.join_key || payload.name, valueType: payload.value_type, description: payload.description || "", tags: payload.tags || {}, owner: payload.owner || "", project: payload.project, }, meta: {}, }); const dataSourcePayloadToResource = (payload) => ({ name: payload.name, type: payload.type, timestampField: payload.timestamp_field, fieldMapping: payload.field_mapping || {}, description: payload.description || "", tags: payload.tags || {}, owner: payload.owner || "", project: payload.project, fileOptions: payload.file_options, bigqueryOptions: payload.bigquery_options, snowflakeOptions: payload.snowflake_options, redshiftOptions: payload.redshift_options, kafkaOptions: payload.kafka_options, sparkOptions: payload.spark_options, }); const featureViewPayloadToResource = (payload) => ({ spec: { name: payload.name, description: payload.description || "", owner: payload.owner || "", entities: payload.entities || [], features: payload.features || [], ttl: payload.ttl, online: payload.online, tags: payload.tags || {}, project: payload.project, batchSource: payload.batch_source ? { name: payload.batch_source } : undefined, }, meta: {}, type: "featureView", }); module.exports = function setupProxy(app) { app.use("/api/v1", express.json()); app.get("/projects-list.json", (_req, res) => { res.json({ ...projectsList, projects: projectsList.projects.map((project) => project.id === "credit_scoring_aws" ? { ...project, registryPath: "/api/v1" } : project, ), }); }); app.get("/api/v1/entities/all", (_req, res) => responseList(res, "entities", state.entities), ); app.get("/api/v1/feature_views/all", (_req, res) => responseList(res, "featureViews", allFeatureViews()), ); app.get("/api/v1/feature_services/all", (_req, res) => responseList(res, "featureServices", state.featureServices), ); app.get("/api/v1/data_sources/all", (_req, res) => responseList(res, "dataSources", state.dataSources), ); app.get("/api/v1/saved_datasets/all", (_req, res) => responseList(res, "savedDatasets", state.savedDatasets), ); app.get("/api/v1/features/all", (_req, res) => responseList(res, "features", allFeatures()), ); app.get("/api/v1/label_views/all", (_req, res) => responseList(res, "featureViews", []), ); app.get("/api/v1/entities", (req, res) => responseList( res, "entities", filterByProject(state.entities, req.query.project), ), ); app.get("/api/v1/feature_views", (req, res) => responseList( res, "featureViews", filterByProject(allFeatureViews(), req.query.project), ), ); app.get("/api/v1/feature_services", (req, res) => responseList( res, "featureServices", filterByProject(state.featureServices, req.query.project), ), ); app.get("/api/v1/data_sources", (req, res) => responseList( res, "dataSources", filterByProject(state.dataSources, req.query.project), ), ); app.get("/api/v1/saved_datasets", (req, res) => responseList( res, "savedDatasets", filterByProject(state.savedDatasets, req.query.project), ), ); app.get("/api/v1/features", (req, res) => responseList(res, "features", allFeatures(req.query.project)), ); app.get("/api/v1/label_views", (_req, res) => responseList(res, "featureViews", []), ); app.get("/api/v1/labels", (_req, res) => responseList(res, "labels", [])); app.get("/api/v1/projects", (_req, res) => responseList(res, "projects", state.projects), ); app.get("/api/v1/permissions", (_req, res) => responseList(res, "permissions", []), ); app.get("/api/v1/metrics/:type", (_req, res) => res.json({})); app.get("/api/v1/entities/:name", (req, res) => { const entity = findByName(state.entities, req.params.name); if (!entity) return res.status(404).json({ detail: "Not found" }); return res.json(entity); }); app.get("/api/v1/feature_views/:name", (req, res) => { const featureView = findByName(allFeatureViews(), req.params.name); if (!featureView) return res.status(404).json({ detail: "Not found" }); return res.json(featureView); }); app.get("/api/v1/feature_services/:name", (req, res) => { const featureService = findByName(state.featureServices, req.params.name); if (!featureService) return res.status(404).json({ detail: "Not found" }); return res.json(featureService); }); app.get("/api/v1/data_sources/:name", (req, res) => { const dataSource = findByName(state.dataSources, req.params.name); if (!dataSource) return res.status(404).json({ detail: "Not found" }); return res.json(dataSource); }); app.get("/api/v1/saved_datasets/:name", (req, res) => { const savedDataset = findByName(state.savedDatasets, req.params.name); if (!savedDataset) return res.status(404).json({ detail: "Not found" }); return res.json(savedDataset); }); app.get("/api/v1/features/:fvName/:featureName", (req, res) => { const featureView = findByName(allFeatureViews(), req.params.fvName); const feature = featureView?.spec?.features?.find( (f) => f.name === req.params.featureName, ); if (!feature) return res.status(404).json({ detail: "Not found" }); return res.json({ featureViewName: req.params.fvName, featureName: req.params.featureName, feature, featureView, }); }); app.post("/api/v1/entities", (req, res) => { const body = req.body || {}; const existingIndex = state.entities.findIndex( (entity) => entity?.spec?.name === body.name, ); const entity = entityPayloadToResource(body); if (existingIndex >= 0) { state.entities[existingIndex] = entity; } else { state.entities.push(entity); } res.json({ name: body.name, project: body.project, status: "applied", }); }); app.post("/api/v1/data_sources", (req, res) => { const body = req.body || {}; const existingIndex = state.dataSources.findIndex( (dataSource) => dataSource?.name === body.name, ); const dataSource = dataSourcePayloadToResource(body); if (existingIndex >= 0) { state.dataSources[existingIndex] = dataSource; } else { state.dataSources.push(dataSource); } res.json({ name: body.name, project: body.project, status: "applied", }); }); app.post("/api/v1/feature_views", (req, res) => { const body = req.body || {}; const existingIndex = state.featureViews.findIndex( (featureView) => featureView?.spec?.name === body.name, ); const featureView = featureViewPayloadToResource(body); if (existingIndex >= 0) { state.featureViews[existingIndex] = featureView; } else { state.featureViews.push(featureView); } res.json({ name: body.name, project: body.project, status: "applied", }); }); app.delete("/api/v1/entities/:name", (req, res) => { state.entities = state.entities.filter( (entity) => entity?.spec?.name !== req.params.name, ); res.json({ name: req.params.name, project: req.query.project, status: "deleted", }); }); app.delete("/api/v1/data_sources/:name", (req, res) => { state.dataSources = state.dataSources.filter( (dataSource) => dataSource?.name !== req.params.name, ); res.json({ name: req.params.name, project: req.query.project, status: "deleted", }); }); app.delete("/api/v1/feature_views/:name", (req, res) => { state.featureViews = state.featureViews.filter( (featureView) => featureView?.spec?.name !== req.params.name, ); res.json({ name: req.params.name, project: req.query.project, status: "deleted", }); }); };