mirror of
https://github.com/ragestudio/comty.git
synced 2025-06-10 02:54:15 +00:00
added marketplace_server
This commit is contained in:
parent
13c968ecea
commit
c3a9ffe80f
1
packages/marketplace_server/.gitignore
vendored
Normal file
1
packages/marketplace_server/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
/static
|
49
packages/marketplace_server/package.json
Normal file
49
packages/marketplace_server/package.json
Normal file
@ -0,0 +1,49 @@
|
||||
{
|
||||
"name": "@comty/marketplace_server",
|
||||
"version": "0.45.2",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
"build": "corenode-cli build",
|
||||
"dev": "cross-env NODE_ENV=development nodemon --ignore dist/ --exec corenode-node ./src/index.js"
|
||||
},
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"7zip-min": "^1.4.4",
|
||||
"@corenode/utils": "0.28.26",
|
||||
"@foxify/events": "^2.1.0",
|
||||
"@octokit/rest": "^19.0.7",
|
||||
"axios": "^1.2.5",
|
||||
"bcrypt": "^5.1.0",
|
||||
"busboy": "^1.6.0",
|
||||
"connect-mongo": "^4.6.0",
|
||||
"content-range": "^2.0.2",
|
||||
"corenode": "0.28.26",
|
||||
"dotenv": "^16.0.3",
|
||||
"formidable": "^2.1.1",
|
||||
"hyper-express": "^6.5.9",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"linebridge": "0.15.12",
|
||||
"live-directory": "^3.0.3",
|
||||
"luxon": "^3.2.1",
|
||||
"merge-files": "^0.1.2",
|
||||
"mime-types": "^2.1.35",
|
||||
"minio": "^7.0.32",
|
||||
"moment": "^2.29.4",
|
||||
"moment-timezone": "^0.5.40",
|
||||
"mongoose": "^6.9.0",
|
||||
"normalize-url": "^8.0.0",
|
||||
"p-map": "^6.0.0",
|
||||
"p-queue": "^7.3.4",
|
||||
"redis": "^4.6.6",
|
||||
"sharp": "^0.31.3",
|
||||
"split-chunk-merge": "^1.0.0",
|
||||
"sucrase": "^3.32.0",
|
||||
"uglify-js": "^3.17.4"
|
||||
},
|
||||
"devDependencies": {
|
||||
"chai": "^4.3.7",
|
||||
"cross-env": "^7.0.3",
|
||||
"mocha": "^10.2.0",
|
||||
"nodemon": "^2.0.15"
|
||||
}
|
||||
}
|
119
packages/marketplace_server/src/api.js
Normal file
119
packages/marketplace_server/src/api.js
Normal file
@ -0,0 +1,119 @@
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
|
||||
import DbManager from "@classes/DbManager"
|
||||
import RedisClient from "@classes/RedisClient"
|
||||
import StorageClient from "@classes/StorageClient"
|
||||
|
||||
import hyperexpress from "hyper-express"
|
||||
|
||||
import pkg from "../package.json"
|
||||
|
||||
export default class WidgetsAPI {
|
||||
server = global.server = new hyperexpress.Server()
|
||||
|
||||
listenIp = process.env.HTTP_LISTEN_IP ?? "0.0.0.0"
|
||||
listenPort = process.env.HTTP_LISTEN_PORT ?? 3040
|
||||
|
||||
internalRouter = new hyperexpress.Router()
|
||||
|
||||
db = new DbManager()
|
||||
|
||||
redis = global.redis = RedisClient()
|
||||
|
||||
storage = global.storage = StorageClient()
|
||||
|
||||
async __registerControllers() {
|
||||
let controllersPath = fs.readdirSync(path.resolve(__dirname, "controllers"))
|
||||
|
||||
for await (const controllerPath of controllersPath) {
|
||||
const controller = require(path.resolve(__dirname, "controllers", controllerPath)).default
|
||||
|
||||
if (!controller) {
|
||||
console.error(`Controller ${controllerPath} not found.`)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
const handler = controller(new hyperexpress.Router())
|
||||
|
||||
if (!handler) {
|
||||
console.error(`Controller ${controllerPath} returning not valid handler.`)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
this.internalRouter.use(handler.path ?? "/", handler.router)
|
||||
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
async __registerInternalMiddlewares() {
|
||||
let middlewaresPath = fs.readdirSync(path.resolve(__dirname, "useMiddlewares"))
|
||||
|
||||
for await (const middlewarePath of middlewaresPath) {
|
||||
const middleware = require(path.resolve(__dirname, "useMiddlewares", middlewarePath)).default
|
||||
|
||||
if (!middleware) {
|
||||
console.error(`Middleware ${middlewarePath} not found.`)
|
||||
|
||||
continue
|
||||
}
|
||||
|
||||
this.server.use(middleware)
|
||||
}
|
||||
}
|
||||
|
||||
__registerInternalRoutes() {
|
||||
this.server.get("/", (req, res) => {
|
||||
return res.status(200).json({
|
||||
name: pkg.name,
|
||||
version: pkg.version,
|
||||
routes: this.__getRegisteredRoutes()
|
||||
})
|
||||
})
|
||||
|
||||
this.server.any("*", (req, res) => {
|
||||
return res.status(404).json({
|
||||
error: "Not found",
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
__getRegisteredRoutes() {
|
||||
return this.internalRouter.routes.map((route) => {
|
||||
return {
|
||||
method: route.method,
|
||||
path: route.pattern,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
async initialize() {
|
||||
const startHrTime = process.hrtime()
|
||||
|
||||
// initialize clients
|
||||
await this.db.initialize()
|
||||
await this.redis.initialize()
|
||||
await this.storage.initialize()
|
||||
|
||||
// register controllers & middlewares
|
||||
await this.__registerInternalRoutes()
|
||||
await this.__registerControllers()
|
||||
await this.__registerInternalMiddlewares()
|
||||
|
||||
// use internal router
|
||||
this.server.use(this.internalRouter)
|
||||
|
||||
// start server
|
||||
await this.server.listen(this.listenPort, this.listenIp)
|
||||
|
||||
// calculate elapsed time
|
||||
const elapsedHrTime = process.hrtime(startHrTime)
|
||||
const elapsedTimeInMs = elapsedHrTime[0] * 1000 + elapsedHrTime[1] / 1e6
|
||||
|
||||
// log server started
|
||||
console.log(`🚀 Server started ready on \n\t - http://${this.listenIp}:${this.listenPort} \n\t - Tooks ${elapsedTimeInMs}ms`)
|
||||
}
|
||||
}
|
58
packages/marketplace_server/src/classes/DbManager/index.js
Executable file
58
packages/marketplace_server/src/classes/DbManager/index.js
Executable file
@ -0,0 +1,58 @@
|
||||
import mongoose from "mongoose"
|
||||
|
||||
function getConnectionConfig(obj) {
|
||||
const { DB_USER, DB_DRIVER, DB_NAME, DB_PWD, DB_HOSTNAME, DB_PORT } = obj
|
||||
|
||||
let auth = [
|
||||
DB_DRIVER ?? "mongodb",
|
||||
"://",
|
||||
]
|
||||
|
||||
if (DB_USER && DB_PWD) {
|
||||
auth.push(`${DB_USER}:${DB_PWD}@`)
|
||||
}
|
||||
|
||||
auth.push(DB_HOSTNAME ?? "localhost")
|
||||
auth.push(`:${DB_PORT ?? "27017"}`)
|
||||
|
||||
if (DB_USER) {
|
||||
auth.push("/?authMechanism=DEFAULT")
|
||||
}
|
||||
|
||||
auth = auth.join("")
|
||||
|
||||
return [
|
||||
auth,
|
||||
{
|
||||
dbName: DB_NAME,
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default class DBManager {
|
||||
initialize = async (config) => {
|
||||
console.log("🔌 Connecting to DB...")
|
||||
|
||||
const dbConfig = getConnectionConfig(config ?? process.env)
|
||||
|
||||
mongoose.set("strictQuery", false)
|
||||
|
||||
const connection = await mongoose.connect(...dbConfig)
|
||||
.catch((err) => {
|
||||
console.log(`❌ Failed to connect to DB, retrying...\n`)
|
||||
console.log(error)
|
||||
|
||||
// setTimeout(() => {
|
||||
// this.initialize()
|
||||
// }, 1000)
|
||||
|
||||
return false
|
||||
})
|
||||
|
||||
if (connection) {
|
||||
console.log(`✅ Connected to DB.`)
|
||||
}
|
||||
}
|
||||
}
|
44
packages/marketplace_server/src/classes/RedisClient/index.js
Normal file
44
packages/marketplace_server/src/classes/RedisClient/index.js
Normal file
@ -0,0 +1,44 @@
|
||||
import { createClient } from "redis"
|
||||
|
||||
function composeURL() {
|
||||
// support for auth
|
||||
let url = "redis://"
|
||||
|
||||
if (process.env.REDIS_PASSWORD && process.env.REDIS_USERNAME) {
|
||||
url += process.env.REDIS_USERNAME + ":" + process.env.REDIS_PASSWORD + "@"
|
||||
}
|
||||
|
||||
url += process.env.REDIS_HOST ?? "localhost"
|
||||
|
||||
if (process.env.REDIS_PORT) {
|
||||
url += ":" + process.env.REDIS_PORT
|
||||
}
|
||||
|
||||
return url
|
||||
}
|
||||
|
||||
export default () => {
|
||||
let client = createClient({
|
||||
url: composeURL(),
|
||||
})
|
||||
|
||||
client.initialize = async () => {
|
||||
console.log("🔌 Connecting to Redis client...")
|
||||
|
||||
await client.connect()
|
||||
|
||||
return client
|
||||
}
|
||||
|
||||
// handle when client disconnects unexpectedly to avoid main crash
|
||||
client.on("error", (error) => {
|
||||
console.error("❌ Redis client error:", error)
|
||||
})
|
||||
|
||||
// handle when client connects
|
||||
client.on("connect", () => {
|
||||
console.log("✅ Redis client connected.")
|
||||
})
|
||||
|
||||
return client
|
||||
}
|
94
packages/marketplace_server/src/classes/StorageClient/index.js
Executable file
94
packages/marketplace_server/src/classes/StorageClient/index.js
Executable file
@ -0,0 +1,94 @@
|
||||
const Minio = require("minio")
|
||||
|
||||
export const generateDefaultBucketPolicy = (payload) => {
|
||||
const { bucketName } = payload
|
||||
|
||||
if (!bucketName) {
|
||||
throw new Error("bucketName is required")
|
||||
}
|
||||
|
||||
return {
|
||||
Version: "2012-10-17",
|
||||
Statement: [
|
||||
{
|
||||
Action: [
|
||||
"s3:GetObject"
|
||||
],
|
||||
Effect: "Allow",
|
||||
Principal: {
|
||||
AWS: [
|
||||
"*"
|
||||
]
|
||||
},
|
||||
Resource: [
|
||||
`arn:aws:s3:::${bucketName}/*`
|
||||
],
|
||||
Sid: ""
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
export class StorageClient extends Minio.Client {
|
||||
constructor(options) {
|
||||
super(options)
|
||||
|
||||
this.defaultBucket = String(options.defaultBucket)
|
||||
this.defaultRegion = String(options.defaultRegion)
|
||||
}
|
||||
|
||||
composeRemoteURL = (key) => {
|
||||
return `${this.protocol}//${this.host}:${this.port}/${this.defaultBucket}/${key}`
|
||||
}
|
||||
|
||||
setDefaultBucketPolicy = async (bucketName) => {
|
||||
const policy = generateDefaultBucketPolicy({ bucketName })
|
||||
|
||||
return this.setBucketPolicy(bucketName, JSON.stringify(policy))
|
||||
}
|
||||
|
||||
initialize = async () => {
|
||||
console.log("🔌 Checking if storage client have default bucket...")
|
||||
|
||||
// check connection with s3
|
||||
const bucketExists = await this.bucketExists(this.defaultBucket).catch(() => {
|
||||
return false
|
||||
})
|
||||
|
||||
if (!bucketExists) {
|
||||
console.warn("🪣 Default bucket not exists! Creating new bucket...")
|
||||
|
||||
await this.makeBucket(this.defaultBucket, "s3")
|
||||
|
||||
// set default bucket policy
|
||||
await this.setDefaultBucketPolicy(this.defaultBucket)
|
||||
}
|
||||
|
||||
// check if default bucket policy exists
|
||||
const bucketPolicy = await this.getBucketPolicy(this.defaultBucket).catch(() => {
|
||||
return null
|
||||
})
|
||||
|
||||
if (!bucketPolicy) {
|
||||
// set default bucket policy
|
||||
await this.setDefaultBucketPolicy(this.defaultBucket)
|
||||
}
|
||||
|
||||
console.log("✅ Storage client is ready.")
|
||||
}
|
||||
}
|
||||
|
||||
export const createStorageClientInstance = (options) => {
|
||||
return new StorageClient({
|
||||
...options,
|
||||
endPoint: process.env.S3_ENDPOINT,
|
||||
port: Number(process.env.S3_PORT),
|
||||
useSSL: toBoolean(process.env.S3_USE_SSL),
|
||||
accessKey: process.env.S3_ACCESS_KEY,
|
||||
secretKey: process.env.S3_SECRET_KEY,
|
||||
defaultBucket: process.env.S3_BUCKET,
|
||||
defaultRegion: process.env.S3_REGION,
|
||||
})
|
||||
}
|
||||
|
||||
export default createStorageClientInstance
|
72
packages/marketplace_server/src/controllers/static/index.js
Normal file
72
packages/marketplace_server/src/controllers/static/index.js
Normal file
@ -0,0 +1,72 @@
|
||||
import path from "path"
|
||||
import fs from "fs"
|
||||
import LiveDirectory from "live-directory"
|
||||
|
||||
function serveStaticFiles(req, res, live_dir) {
|
||||
const path = req.path.replace("/static", "")
|
||||
|
||||
const asset = live_dir.get(path)
|
||||
|
||||
if (!asset) {
|
||||
return res.status(404).send("Not Found")
|
||||
}
|
||||
|
||||
if (asset.cached) {
|
||||
return res.send(asset.content)
|
||||
} else {
|
||||
const readable = asset.stream()
|
||||
|
||||
return readable.pipe(res)
|
||||
}
|
||||
}
|
||||
|
||||
async function serveRemoteStatic(req, res) {
|
||||
global.storage.getObject(process.env.S3_BUCKET, req.path, (err, dataStream) => {
|
||||
if (err) {
|
||||
console.log(err)
|
||||
return res.status(404).send("Not Found")
|
||||
}
|
||||
|
||||
// on end of stream, dispath res.on("finish")
|
||||
dataStream.on("end", () => {
|
||||
res.emit("finish")
|
||||
return res.end()
|
||||
})
|
||||
|
||||
return dataStream.pipe(res)
|
||||
})
|
||||
}
|
||||
|
||||
async function syncFolder(dir) {
|
||||
const files = await fs.promises.readdir(dir)
|
||||
|
||||
for await (const file of files) {
|
||||
const filePath = path.resolve(dir, file)
|
||||
|
||||
const stat = fs.statSync(filePath)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
await syncFolder(filePath)
|
||||
} else {
|
||||
const fileContent = await fs.promises.readFile(filePath)
|
||||
|
||||
await global.storage.putObject(process.env.S3_BUCKET, filePath.replace(process.cwd(), ""), fileContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default (router) => {
|
||||
const StaticDirectory = new LiveDirectory(path.resolve(process.cwd(), "static"), {
|
||||
static: true
|
||||
})
|
||||
|
||||
//const static_dir = path.resolve(process.cwd(), "static")
|
||||
//syncFolder(static_dir)
|
||||
|
||||
router.get("*", (req, res) => serveRemoteStatic(req, res, StaticDirectory))
|
||||
|
||||
return {
|
||||
path: "/static/",
|
||||
router,
|
||||
}
|
||||
}
|
14
packages/marketplace_server/src/controllers/widgets/index.js
Normal file
14
packages/marketplace_server/src/controllers/widgets/index.js
Normal file
@ -0,0 +1,14 @@
|
||||
import path from "path"
|
||||
import createRoutesFromDirectory from "@utils/createRoutesFromDirectory"
|
||||
|
||||
export default (router) => {
|
||||
// create a file based router
|
||||
const routesPath = path.resolve(__dirname, "routes")
|
||||
|
||||
router = createRoutesFromDirectory("routes", routesPath, router)
|
||||
|
||||
return {
|
||||
path: "/widgets",
|
||||
router,
|
||||
}
|
||||
}
|
@ -0,0 +1,168 @@
|
||||
import { Widget } from "@models"
|
||||
|
||||
import { transform } from "sucrase"
|
||||
import UglifyJS from "uglify-js"
|
||||
|
||||
import resolveUrl from "@utils/resolveUrl"
|
||||
import replaceImportsWithRemoteURL from "@utils/replaceImportsWithRemoteURL"
|
||||
|
||||
async function compileWidgetCode(widgetCode, manifest, rootURL) {
|
||||
if (!widgetCode) {
|
||||
throw new Error("Widget code not defined.")
|
||||
}
|
||||
|
||||
if (!manifest) {
|
||||
throw new Error("Manifest not defined.")
|
||||
}
|
||||
|
||||
if (!rootURL) {
|
||||
throw new Error("Root URL not defined.")
|
||||
}
|
||||
|
||||
let renderComponentName = null
|
||||
let cssFiles = []
|
||||
|
||||
// inject react with cdn
|
||||
widgetCode = `import React from "https://cdn.skypack.dev/react@17?dts" \n${widgetCode}`
|
||||
|
||||
widgetCode = await replaceImportsWithRemoteURL(widgetCode, resolveUrl(rootURL, manifest.entryFile))
|
||||
|
||||
// remove css imports and append to manifest (Only its used in the entry file)
|
||||
widgetCode = widgetCode.replace(/import\s+["'](.*)\.css["']/g, (match, p1) => {
|
||||
cssFiles.push(resolveUrl(rootURL, `${p1}.css`))
|
||||
|
||||
return ""
|
||||
})
|
||||
|
||||
// transform jsx to js
|
||||
widgetCode = await transform(widgetCode, {
|
||||
transforms: ["jsx"],
|
||||
//jsxRuntime: "automatic",
|
||||
//production: true,
|
||||
}).code
|
||||
|
||||
// search export default and get the name of the function/const/class
|
||||
const exportDefaultRegex = /export\s+default\s+(?:function|const|class)\s+([a-zA-Z0-9]+)/g
|
||||
|
||||
const exportDefaultMatch = exportDefaultRegex.exec(widgetCode)
|
||||
|
||||
if (exportDefaultMatch) {
|
||||
renderComponentName = exportDefaultMatch[1]
|
||||
}
|
||||
|
||||
// remove export default keywords
|
||||
widgetCode = widgetCode.replace("export default", "")
|
||||
|
||||
|
||||
let manifestProcessed = {
|
||||
...manifest,
|
||||
}
|
||||
|
||||
manifestProcessed.cssFiles = cssFiles
|
||||
manifestProcessed.entryFile = resolveUrl(rootURL, manifest.entryFile)
|
||||
manifestProcessed.iconUrl = resolveUrl(rootURL, manifest.iconUrl)
|
||||
|
||||
let result = `
|
||||
${widgetCode}
|
||||
|
||||
export default {
|
||||
manifest: ${JSON.stringify(manifestProcessed)},
|
||||
renderComponent: ${renderComponentName},
|
||||
}
|
||||
`
|
||||
|
||||
// minify code
|
||||
result = UglifyJS.minify(result, {
|
||||
compress: true,
|
||||
mangle: true,
|
||||
}).code
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export default async (req, res) => {
|
||||
const widgetId = req.params.widgetId
|
||||
|
||||
const useCache = req.query["use-cache"] ? toBoolean(req.query["use-cache"]) : true
|
||||
|
||||
//console.log(`📦 Getting widget code [${widgetId}], using cache ${useCache}`)
|
||||
|
||||
// try to find Widget
|
||||
const widget = await Widget.findOne({
|
||||
_id: widgetId,
|
||||
}).catch(() => {
|
||||
return null
|
||||
})
|
||||
|
||||
if (!widget) {
|
||||
return res.status(404).json({
|
||||
error: "Widget not found.",
|
||||
})
|
||||
}
|
||||
|
||||
if (!widget.manifest.entryFile) {
|
||||
return res.status(404).json({
|
||||
error: "Widget entry file not defined",
|
||||
})
|
||||
}
|
||||
|
||||
let widgetCode = null
|
||||
|
||||
// get origin from request url
|
||||
const origin = req.protocol + "://" + req.get("host")
|
||||
const remotePath = `/static/${widgetId}/src`
|
||||
|
||||
const remoteEntyFilePath = resolveUrl(remotePath, widget.manifest.entryFile)
|
||||
|
||||
if (useCache) {
|
||||
widgetCode = await global.redis.get(`widget:${widgetId}`)
|
||||
}
|
||||
|
||||
if (!widgetCode) {
|
||||
try {
|
||||
widgetCode = await new Promise(async (resolve, reject) => {
|
||||
await global.storage.getObject(process.env.S3_BUCKET, remoteEntyFilePath, (error, dataStream) => {
|
||||
if (error) {
|
||||
return false
|
||||
}
|
||||
|
||||
let data = ""
|
||||
|
||||
dataStream.on("data", (chunk) => {
|
||||
data += chunk
|
||||
})
|
||||
|
||||
dataStream.on("end", () => {
|
||||
resolve(data)
|
||||
})
|
||||
})
|
||||
})
|
||||
} catch (error) {
|
||||
return res.status(500).json({
|
||||
error: error.message,
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
console.log("🔌 Compiling widget code...")
|
||||
|
||||
widgetCode = await compileWidgetCode(widgetCode, widget.manifest, resolveUrl(origin, remotePath))
|
||||
|
||||
await global.redis.set(`widget:${widgetId}`, widgetCode)
|
||||
} catch (error) {
|
||||
return res.status(500).json({
|
||||
message: "Unable to transform widget code.",
|
||||
error: error.message,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if (!widgetCode) {
|
||||
return res.status(404).json({
|
||||
error: "Widget not found.",
|
||||
})
|
||||
}
|
||||
|
||||
res.setHeader("Content-Type", "application/javascript")
|
||||
return res.status(200).send(widgetCode)
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
import { Widget } from "@models"
|
||||
|
||||
export default async (req, res) => {
|
||||
const widget_id = req.params.widgetId
|
||||
|
||||
const widget = await Widget.findOne({
|
||||
_id: widget_id,
|
||||
}).catch(() => {
|
||||
return false
|
||||
})
|
||||
|
||||
if (!widget) {
|
||||
return res.status(404).json({
|
||||
error: "Widget not found",
|
||||
})
|
||||
}
|
||||
|
||||
return res.status(200).json({
|
||||
...widget.manifest,
|
||||
user_id: widget.user_id,
|
||||
_id: widget_id,
|
||||
})
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
import { Widget } from "@models"
|
||||
|
||||
export default async (req, res) => {
|
||||
let { limit = 20, offset = 0, keywords } = req.query
|
||||
|
||||
keywords = JSON.parse(keywords ?? "{}")
|
||||
|
||||
// remove empty keywords
|
||||
Object.keys(keywords).forEach((key) => {
|
||||
if (keywords[key] === "") {
|
||||
delete keywords[key]
|
||||
}
|
||||
})
|
||||
|
||||
console.log("Searching with keywords:", keywords)
|
||||
|
||||
const query = {
|
||||
public: true,
|
||||
}
|
||||
|
||||
// inclide keywords for search in manifest
|
||||
Object.keys(keywords).forEach((key) => {
|
||||
query[`manifest.${key}`] = {
|
||||
$regex: keywords[key],
|
||||
$options: "i",
|
||||
}
|
||||
})
|
||||
|
||||
let widgets = await Widget.find(query)
|
||||
.limit(Number(limit))
|
||||
.skip(Number(offset))
|
||||
|
||||
widgets = widgets.map((widget) => {
|
||||
widget.manifest._id = widget._id
|
||||
widget.manifest.user_id = widget.user_id
|
||||
|
||||
return widget
|
||||
})
|
||||
|
||||
return res.json(widgets)
|
||||
}
|
@ -0,0 +1,15 @@
|
||||
import { Widget } from "@models"
|
||||
|
||||
export default async (req, res) => {
|
||||
const { user_id } = req.params
|
||||
const { limit = 20, offset = 0 } = req.query
|
||||
|
||||
const widgets = await Widget.find({
|
||||
user_id,
|
||||
public: true,
|
||||
})
|
||||
.limit(Number(limit))
|
||||
.skip(Number(offset))
|
||||
|
||||
return res.json(widgets)
|
||||
}
|
@ -0,0 +1,5 @@
|
||||
export default (req, res) => {
|
||||
return res.status(200).json({
|
||||
test: "Testing endpoints by route files"
|
||||
})
|
||||
}
|
56
packages/marketplace_server/src/index.js
Normal file
56
packages/marketplace_server/src/index.js
Normal file
@ -0,0 +1,56 @@
|
||||
require("dotenv").config()
|
||||
import { webcrypto as crypto } from "crypto"
|
||||
import path from "path"
|
||||
import { registerBaseAliases } from "linebridge/dist/server"
|
||||
|
||||
registerBaseAliases(undefined, {
|
||||
"@services": path.resolve(__dirname, "services"),
|
||||
})
|
||||
|
||||
// patches
|
||||
const { Buffer } = require("buffer")
|
||||
|
||||
global.b64Decode = (data) => {
|
||||
return Buffer.from(data, "base64").toString("utf-8")
|
||||
}
|
||||
global.b64Encode = (data) => {
|
||||
return Buffer.from(data, "utf-8").toString("base64")
|
||||
}
|
||||
|
||||
global.nanoid = (t = 21) => crypto.getRandomValues(new Uint8Array(t)).reduce(((t, e) => t += (e &= 63) < 36 ? e.toString(36) : e < 62 ? (e - 26).toString(36).toUpperCase() : e > 62 ? "-" : "_"), "");
|
||||
|
||||
Array.prototype.updateFromObjectKeys = function (obj) {
|
||||
this.forEach((value, index) => {
|
||||
if (obj[value] !== undefined) {
|
||||
this[index] = obj[value]
|
||||
}
|
||||
})
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
global.toBoolean = (value) => {
|
||||
if (typeof value === "boolean") {
|
||||
return value
|
||||
}
|
||||
|
||||
if (typeof value === "string") {
|
||||
return value.toLowerCase() === "true"
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
global.isProduction = process.env.NODE_ENV === "production"
|
||||
|
||||
import API from "./api"
|
||||
|
||||
async function main() {
|
||||
const mainAPI = new API()
|
||||
|
||||
await mainAPI.initialize()
|
||||
}
|
||||
|
||||
main().catch((error) => {
|
||||
console.error(`🆘 [FATAL ERROR] >`, error)
|
||||
})
|
19
packages/marketplace_server/src/models/index.js
Executable file
19
packages/marketplace_server/src/models/index.js
Executable file
@ -0,0 +1,19 @@
|
||||
import mongoose, { Schema } from "mongoose"
|
||||
import fs from "fs"
|
||||
import path from "path"
|
||||
|
||||
function generateModels() {
|
||||
let models = {}
|
||||
|
||||
const dirs = fs.readdirSync(__dirname).filter(file => file !== "index.js")
|
||||
|
||||
dirs.forEach((file) => {
|
||||
const model = require(path.join(__dirname, file)).default
|
||||
|
||||
models[model.name] = mongoose.model(model.name, new Schema(model.schema), model.collection)
|
||||
})
|
||||
|
||||
return models
|
||||
}
|
||||
|
||||
module.exports = generateModels()
|
24
packages/marketplace_server/src/models/widget/index.js
Normal file
24
packages/marketplace_server/src/models/widget/index.js
Normal file
@ -0,0 +1,24 @@
|
||||
export default {
|
||||
name: "Widget",
|
||||
collection: "widgets",
|
||||
schema: {
|
||||
manifest: {
|
||||
type: Object,
|
||||
required: true,
|
||||
},
|
||||
user_id: {
|
||||
type: String,
|
||||
required: true,
|
||||
},
|
||||
public: {
|
||||
type: Boolean,
|
||||
default: true,
|
||||
},
|
||||
created_at: {
|
||||
type: Date,
|
||||
},
|
||||
updated_at: {
|
||||
type: Date,
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import cors from "cors"
|
||||
|
||||
export default cors({
|
||||
origin: "*",
|
||||
methods: ["GET", "POST", "PUT", "DELETE", "PATCH", "OPTIONS", "HEAD", "CONNECT", "TRACE"],
|
||||
preflightContinue: false,
|
||||
optionsSuccessStatus: 204,
|
||||
})
|
@ -0,0 +1,14 @@
|
||||
export default (req, res, next) => {
|
||||
const startHrTime = process.hrtime()
|
||||
|
||||
res.on("finish", () => {
|
||||
const elapsedHrTime = process.hrtime(startHrTime)
|
||||
const elapsedTimeInMs = elapsedHrTime[0] * 1000 + elapsedHrTime[1] / 1e6
|
||||
|
||||
res._responseTimeMs = elapsedTimeInMs
|
||||
|
||||
console.log(`${req.method} ${res._status_code ?? res.statusCode ?? 200} ${req.url} ${elapsedTimeInMs}ms`)
|
||||
})
|
||||
|
||||
next()
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
import fs from "fs"
|
||||
|
||||
function createRoutesFromDirectory(startFrom, directoryPath, router) {
|
||||
const files = fs.readdirSync(directoryPath)
|
||||
|
||||
files.forEach((file) => {
|
||||
const filePath = `${directoryPath}/${file}`
|
||||
|
||||
const stat = fs.statSync(filePath)
|
||||
|
||||
if (stat.isDirectory()) {
|
||||
createRoutesFromDirectory(startFrom, filePath, router)
|
||||
} else if (file.endsWith(".js")) {
|
||||
let splitedFilePath = filePath.split("/")
|
||||
|
||||
// slice the startFrom path
|
||||
splitedFilePath = splitedFilePath.slice(splitedFilePath.indexOf(startFrom) + 1)
|
||||
|
||||
const method = splitedFilePath[0]
|
||||
|
||||
let route = splitedFilePath.slice(1, splitedFilePath.length).join("/")
|
||||
|
||||
route = route.replace(".jsx", "")
|
||||
route = route.replace(".js", "")
|
||||
route = route.replace(".ts", "")
|
||||
route = route.replace(".tsx", "")
|
||||
|
||||
if (route === "index") {
|
||||
route = "/"
|
||||
} else {
|
||||
route = `/${route}`
|
||||
}
|
||||
|
||||
let handler = require(filePath)
|
||||
|
||||
handler = handler.default || handler
|
||||
|
||||
router[method](route, handler)
|
||||
}
|
||||
})
|
||||
|
||||
return router
|
||||
}
|
||||
|
||||
export default createRoutesFromDirectory
|
@ -0,0 +1,18 @@
|
||||
import resolveUrl from "@utils/resolveUrl"
|
||||
|
||||
export default (code, rootURL) => {
|
||||
const importRegex = /import\s+(?:(?:([\w*\s{},]*)\s+from\s+)?["']([^"']*)["']|["']([^"']*)["'])/g
|
||||
|
||||
// replaces all imports with absolute paths
|
||||
const absoluteImportCode = code.replace(importRegex, (match, p1, p2) => {
|
||||
let resolved = resolveUrl(rootURL, p2)
|
||||
|
||||
if (!p1) {
|
||||
return `import "${resolved}"`
|
||||
}
|
||||
|
||||
return `import ${p1} from "${resolved}"`
|
||||
})
|
||||
|
||||
return absoluteImportCode
|
||||
}
|
11
packages/marketplace_server/src/utils/resolveUrl/index.js
Normal file
11
packages/marketplace_server/src/utils/resolveUrl/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
export default (from, to) => {
|
||||
const resolvedUrl = new URL(to, new URL(from, "resolve://"))
|
||||
|
||||
if (resolvedUrl.protocol === "resolve:") {
|
||||
const { pathname, search, hash } = resolvedUrl
|
||||
|
||||
return pathname + search + hash
|
||||
}
|
||||
|
||||
return resolvedUrl.toString()
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user