Allow the ability to completely disable the marketplace.
This commit is contained in:
parent
643fbbe90d
commit
4887562cc7
8 changed files with 277 additions and 245 deletions
|
|
@ -10,6 +10,7 @@ import icon from "astro-icon";
|
||||||
import { defineConfig, envField } from "astro/config";
|
import { defineConfig, envField } from "astro/config";
|
||||||
import { viteStaticCopy } from "vite-plugin-static-copy";
|
import { viteStaticCopy } from "vite-plugin-static-copy";
|
||||||
import { version } from "./package.json";
|
import { version } from "./package.json";
|
||||||
|
import { parsedDoc } from "./server/config.js";
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
experimental: {
|
experimental: {
|
||||||
env: {
|
env: {
|
||||||
|
|
@ -19,6 +20,12 @@ export default defineConfig({
|
||||||
access: "public",
|
access: "public",
|
||||||
optional: true,
|
optional: true,
|
||||||
default: version
|
default: version
|
||||||
|
}),
|
||||||
|
MARKETPLACE_ENABLED: envField.boolean({
|
||||||
|
context: 'client',
|
||||||
|
access: 'public',
|
||||||
|
optional: true,
|
||||||
|
default: parsedDoc.marketplace.enabled
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,7 +2,7 @@ import { fileURLToPath } from "node:url";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
import ora from "ora";
|
import ora from "ora";
|
||||||
import { ModelStatic } from "sequelize";
|
import { ModelStatic } from "sequelize";
|
||||||
import { Catalog, CatalogModel } from "./server.js";
|
import { Catalog, CatalogModel } from "./marketplace.js";
|
||||||
|
|
||||||
interface Items extends Omit<Catalog, "background_video" | "background_image"> {
|
interface Items extends Omit<Catalog, "background_video" | "background_image"> {
|
||||||
background_video?: string;
|
background_video?: string;
|
||||||
|
|
|
||||||
225
server/marketplace.ts
Normal file
225
server/marketplace.ts
Normal file
|
|
@ -0,0 +1,225 @@
|
||||||
|
import { Sequelize, Model, InferAttributes, InferCreationAttributes, DataTypes } from "sequelize";
|
||||||
|
import { parsedDoc } from "./config.js";
|
||||||
|
import { FastifyInstance, FastifyRequest } from "fastify";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { pipeline } from "node:stream/promises";
|
||||||
|
import { createWriteStream } from "node:fs";
|
||||||
|
import { access, mkdir, constants } from "node:fs/promises";
|
||||||
|
|
||||||
|
const db = new Sequelize(parsedDoc.db.name, parsedDoc.db.username, parsedDoc.db.password, {
|
||||||
|
host: parsedDoc.db.postgres ? `${parsedDoc.postgres.domain}` : "localhost",
|
||||||
|
port: parsedDoc.db.postgres ? parsedDoc.postgres.port : undefined,
|
||||||
|
dialect: parsedDoc.db.postgres ? "postgres" : "sqlite",
|
||||||
|
logging: parsedDoc.server.server.logging,
|
||||||
|
storage: "database.sqlite" //this is sqlite only
|
||||||
|
});
|
||||||
|
|
||||||
|
type CatalogType = "theme" | "plugin";
|
||||||
|
|
||||||
|
interface Catalog {
|
||||||
|
package_name: string;
|
||||||
|
title: string;
|
||||||
|
description: string;
|
||||||
|
author: string;
|
||||||
|
image: string;
|
||||||
|
tags: object;
|
||||||
|
version: string;
|
||||||
|
background_image: string;
|
||||||
|
background_video: string;
|
||||||
|
payload: string;
|
||||||
|
type: CatalogType;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CatalogModel
|
||||||
|
extends Catalog,
|
||||||
|
Model<InferAttributes<CatalogModel>, InferCreationAttributes<CatalogModel>> {}
|
||||||
|
|
||||||
|
const catalogAssets = db.define<CatalogModel>("catalog_assets", {
|
||||||
|
package_name: { type: DataTypes.STRING, unique: true },
|
||||||
|
title: { type: DataTypes.TEXT },
|
||||||
|
description: { type: DataTypes.TEXT },
|
||||||
|
author: { type: DataTypes.TEXT },
|
||||||
|
image: { type: DataTypes.TEXT },
|
||||||
|
tags: { type: DataTypes.JSON, allowNull: true },
|
||||||
|
version: { type: DataTypes.TEXT },
|
||||||
|
background_image: { type: DataTypes.TEXT, allowNull: true },
|
||||||
|
background_video: { type: DataTypes.TEXT, allowNull: true },
|
||||||
|
payload: { type: DataTypes.TEXT },
|
||||||
|
type: { type: DataTypes.TEXT }
|
||||||
|
});
|
||||||
|
|
||||||
|
function marketplaceAPI(app: FastifyInstance) {
|
||||||
|
app.get("/api", (request, reply) => {
|
||||||
|
reply.send({ Server: "Active" });
|
||||||
|
});
|
||||||
|
|
||||||
|
// This API returns a list of the assets in the database (SW plugins and themes).
|
||||||
|
// It also returns the number of pages in the database.
|
||||||
|
// It can take a `?page=x` argument to display a different page, with a limit of 20 assets per page.
|
||||||
|
type CatalogAssetsReq = FastifyRequest<{ Querystring: { page: string } }>;
|
||||||
|
app.get("/api/catalog-assets/", async (request: CatalogAssetsReq, reply) => {
|
||||||
|
try {
|
||||||
|
const { page } = request.query;
|
||||||
|
const pageNum: number = parseInt(page, 10) || 1;
|
||||||
|
if (pageNum < 1) {
|
||||||
|
reply.status(400).send({ error: "Page must be a positive number!" });
|
||||||
|
}
|
||||||
|
const offset = (pageNum - 1) * 20;
|
||||||
|
const totalItems = await catalogAssets.count();
|
||||||
|
const dbAssets = await catalogAssets.findAll({ offset: offset, limit: 20 });
|
||||||
|
const assets = dbAssets.reduce((acc, asset) => {
|
||||||
|
acc[asset.package_name] = {
|
||||||
|
title: asset.title,
|
||||||
|
description: asset.description,
|
||||||
|
author: asset.author,
|
||||||
|
image: asset.image,
|
||||||
|
tags: asset.tags,
|
||||||
|
version: asset.version,
|
||||||
|
background_image: asset.background_image,
|
||||||
|
background_video: asset.background_video,
|
||||||
|
payload: asset.payload,
|
||||||
|
type: asset.type
|
||||||
|
};
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
return reply.send({ assets, pages: Math.ceil(totalItems / 20) });
|
||||||
|
} catch (error) {
|
||||||
|
return reply.status(500).send({ error: "An error occured" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
type PackageReq = FastifyRequest<{ Params: { package: string } }>;
|
||||||
|
app.get("/api/packages/:package", async (request: PackageReq, reply) => {
|
||||||
|
try {
|
||||||
|
const packageRow = await catalogAssets.findOne({
|
||||||
|
where: { package_name: request.params.package }
|
||||||
|
});
|
||||||
|
if (!packageRow) return reply.status(404).send({ error: "Package not found!" });
|
||||||
|
const details = {
|
||||||
|
title: packageRow.get("title"),
|
||||||
|
description: packageRow.get("description"),
|
||||||
|
image: packageRow.get("image"),
|
||||||
|
author: packageRow.get("author"),
|
||||||
|
tags: packageRow.get("tags"),
|
||||||
|
version: packageRow.get("version"),
|
||||||
|
background_image: packageRow.get("background_image"),
|
||||||
|
background_video: packageRow.get("background_video"),
|
||||||
|
payload: packageRow.get("payload"),
|
||||||
|
type: packageRow.get("type")
|
||||||
|
};
|
||||||
|
reply.send(details);
|
||||||
|
} catch (error) {
|
||||||
|
reply.status(500).send({ error: "An unexpected error occured" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
type UploadReq = FastifyRequest<{ Headers: { psk: string; packagename: string } }>;
|
||||||
|
type CreateReq = FastifyRequest<{
|
||||||
|
Headers: { psk: string };
|
||||||
|
Body: {
|
||||||
|
uuid: string;
|
||||||
|
title: string;
|
||||||
|
image: string;
|
||||||
|
author: string;
|
||||||
|
version: string;
|
||||||
|
description: string;
|
||||||
|
tags: object | any;
|
||||||
|
payload: string;
|
||||||
|
background_video: string;
|
||||||
|
background_image: string;
|
||||||
|
type: CatalogType;
|
||||||
|
};
|
||||||
|
}>;
|
||||||
|
interface VerifyStatus {
|
||||||
|
status: number;
|
||||||
|
error?: Error;
|
||||||
|
}
|
||||||
|
async function verifyReq(
|
||||||
|
request: UploadReq | CreateReq,
|
||||||
|
upload: Boolean,
|
||||||
|
data: any
|
||||||
|
): Promise<VerifyStatus> {
|
||||||
|
if (parsedDoc.marketplace.enabled === false) {
|
||||||
|
return { status: 500, error: new Error("Marketplace Is disabled!") };
|
||||||
|
} else if (request.headers.psk !== parsedDoc.marketplace.psk) {
|
||||||
|
return { status: 403, error: new Error("PSK isn't correct!") };
|
||||||
|
} else if (upload && !request.headers.packagename) {
|
||||||
|
return { status: 500, error: new Error("No packagename defined!") };
|
||||||
|
} else if (upload && !data) {
|
||||||
|
return { status: 400, error: new Error("No file uploaded!") };
|
||||||
|
} else {
|
||||||
|
return { status: 200 };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
app.post("/api/upload-asset", async (request: UploadReq, reply) => {
|
||||||
|
const data = await request.file();
|
||||||
|
const verify: VerifyStatus = await verifyReq(request, true, data);
|
||||||
|
if (verify.error !== undefined) {
|
||||||
|
reply.status(verify.status).send({ status: verify.error.message });
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
await pipeline(
|
||||||
|
data.file,
|
||||||
|
createWriteStream(
|
||||||
|
fileURLToPath(
|
||||||
|
new URL(
|
||||||
|
`../database_assets/${request.headers.packagename}/${data.filename}`,
|
||||||
|
import.meta.url
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
return reply
|
||||||
|
.status(500)
|
||||||
|
.send({ status: `File couldn't be uploaded! (Package most likely doesn't exist)` });
|
||||||
|
}
|
||||||
|
return reply.status(verify.status).send({ status: "File uploaded successfully!" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
app.post("/api/create-package", async (request: CreateReq, reply) => {
|
||||||
|
const verify: VerifyStatus = await verifyReq(request, false, undefined);
|
||||||
|
if (verify.error !== undefined) {
|
||||||
|
reply.status(verify.status).send({ status: verify.error.message });
|
||||||
|
} else {
|
||||||
|
const body: Catalog = {
|
||||||
|
package_name: request.body.uuid,
|
||||||
|
title: request.body.title,
|
||||||
|
image: request.body.image,
|
||||||
|
author: request.body.author,
|
||||||
|
version: request.body.version,
|
||||||
|
description: request.body.description,
|
||||||
|
tags: request.body.tags,
|
||||||
|
payload: request.body.payload,
|
||||||
|
background_video: request.body.background_video,
|
||||||
|
background_image: request.body.background_image,
|
||||||
|
type: request.body.type as CatalogType
|
||||||
|
};
|
||||||
|
await catalogAssets.create({
|
||||||
|
package_name: body.package_name,
|
||||||
|
title: body.title,
|
||||||
|
image: body.image,
|
||||||
|
author: body.author,
|
||||||
|
version: body.version,
|
||||||
|
description: body.description,
|
||||||
|
tags: body.tags,
|
||||||
|
payload: body.payload,
|
||||||
|
background_video: body.background_video,
|
||||||
|
background_image: body.background_image,
|
||||||
|
type: body.type
|
||||||
|
});
|
||||||
|
const assets = fileURLToPath(new URL("../database_assets", import.meta.url));
|
||||||
|
try {
|
||||||
|
await access(`${assets}/${body.package_name}/`, constants.F_OK);
|
||||||
|
return reply.status(500).send({ status: "Package already exists!" });
|
||||||
|
} catch (err) {
|
||||||
|
await mkdir(`${assets}/${body.package_name}/`);
|
||||||
|
return reply.status(verify.status).send({ status: "Package created successfully!" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { marketplaceAPI, db, catalogAssets, Catalog, CatalogModel }
|
||||||
222
server/server.ts
222
server/server.ts
|
|
@ -14,6 +14,7 @@ import { handler as ssrHandler } from "../dist/server/entry.mjs";
|
||||||
import { parsedDoc } from "./config.js";
|
import { parsedDoc } from "./config.js";
|
||||||
import { setupDB } from "./dbSetup.js";
|
import { setupDB } from "./dbSetup.js";
|
||||||
import { serverFactory } from "./serverFactory.js";
|
import { serverFactory } from "./serverFactory.js";
|
||||||
|
import { marketplaceAPI, catalogAssets } from "./marketplace.js";
|
||||||
|
|
||||||
const app = Fastify({
|
const app = Fastify({
|
||||||
logger: parsedDoc.server.server.logging,
|
logger: parsedDoc.server.server.logging,
|
||||||
|
|
@ -21,47 +22,6 @@ const app = Fastify({
|
||||||
ignoreTrailingSlash: true,
|
ignoreTrailingSlash: true,
|
||||||
serverFactory: serverFactory
|
serverFactory: serverFactory
|
||||||
});
|
});
|
||||||
const db = new Sequelize(parsedDoc.db.name, parsedDoc.db.username, parsedDoc.db.password, {
|
|
||||||
host: parsedDoc.db.postgres ? `${parsedDoc.postgres.domain}` : "localhost",
|
|
||||||
port: parsedDoc.db.postgres ? parsedDoc.postgres.port : undefined,
|
|
||||||
dialect: parsedDoc.db.postgres ? "postgres" : "sqlite",
|
|
||||||
logging: parsedDoc.server.server.logging,
|
|
||||||
storage: "database.sqlite" //this is sqlite only
|
|
||||||
});
|
|
||||||
|
|
||||||
type CatalogType = "theme" | "plugin";
|
|
||||||
|
|
||||||
interface Catalog {
|
|
||||||
package_name: string;
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
author: string;
|
|
||||||
image: string;
|
|
||||||
tags: object;
|
|
||||||
version: string;
|
|
||||||
background_image: string;
|
|
||||||
background_video: string;
|
|
||||||
payload: string;
|
|
||||||
type: CatalogType;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface CatalogModel
|
|
||||||
extends Catalog,
|
|
||||||
Model<InferAttributes<CatalogModel>, InferCreationAttributes<CatalogModel>> {}
|
|
||||||
|
|
||||||
const catalogAssets = db.define<CatalogModel>("catalog_assets", {
|
|
||||||
package_name: { type: DataTypes.STRING, unique: true },
|
|
||||||
title: { type: DataTypes.TEXT },
|
|
||||||
description: { type: DataTypes.TEXT },
|
|
||||||
author: { type: DataTypes.TEXT },
|
|
||||||
image: { type: DataTypes.TEXT },
|
|
||||||
tags: { type: DataTypes.JSON, allowNull: true },
|
|
||||||
version: { type: DataTypes.TEXT },
|
|
||||||
background_image: { type: DataTypes.TEXT, allowNull: true },
|
|
||||||
background_video: { type: DataTypes.TEXT, allowNull: true },
|
|
||||||
payload: { type: DataTypes.TEXT },
|
|
||||||
type: { type: DataTypes.TEXT }
|
|
||||||
});
|
|
||||||
|
|
||||||
await app.register(fastifyCompress, {
|
await app.register(fastifyCompress, {
|
||||||
encodings: ["br", "gzip", "deflate"]
|
encodings: ["br", "gzip", "deflate"]
|
||||||
|
|
@ -74,186 +34,18 @@ await app.register(fastifyStatic, {
|
||||||
decorateReply: false
|
decorateReply: false
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//Our marketplace API. Not middleware as I don't want to deal with that LOL. Just a function that passes our app to it.
|
||||||
|
if (parsedDoc.marketplace.enabled) {
|
||||||
await app.register(fastifyStatic, {
|
await app.register(fastifyStatic, {
|
||||||
root: fileURLToPath(new URL("../database_assets", import.meta.url)),
|
root: fileURLToPath(new URL("../database_assets", import.meta.url)),
|
||||||
prefix: "/packages/",
|
prefix: "/packages/",
|
||||||
decorateReply: false
|
decorateReply: false
|
||||||
});
|
});
|
||||||
|
marketplaceAPI(app);
|
||||||
|
}
|
||||||
|
|
||||||
await app.register(fastifyMiddie);
|
await app.register(fastifyMiddie);
|
||||||
|
|
||||||
app.get("/api", (request, reply) => {
|
|
||||||
reply.send({ Server: "Active" });
|
|
||||||
});
|
|
||||||
|
|
||||||
// This API returns a list of the assets in the database (SW plugins and themes).
|
|
||||||
// It also returns the number of pages in the database.
|
|
||||||
// It can take a `?page=x` argument to display a different page, with a limit of 20 assets per page.
|
|
||||||
type CatalogAssetsReq = FastifyRequest<{ Querystring: { page: string } }>;
|
|
||||||
app.get("/api/catalog-assets/", async (request: CatalogAssetsReq, reply) => {
|
|
||||||
try {
|
|
||||||
const { page } = request.query;
|
|
||||||
const pageNum: number = parseInt(page, 10) || 1;
|
|
||||||
if (pageNum < 1) {
|
|
||||||
reply.status(400).send({ error: "Page must be a positive number!" });
|
|
||||||
}
|
|
||||||
const offset = (pageNum - 1) * 20;
|
|
||||||
const totalItems = await catalogAssets.count();
|
|
||||||
const dbAssets = await catalogAssets.findAll({ offset: offset, limit: 20 });
|
|
||||||
const assets = dbAssets.reduce((acc, asset) => {
|
|
||||||
acc[asset.package_name] = {
|
|
||||||
title: asset.title,
|
|
||||||
description: asset.description,
|
|
||||||
author: asset.author,
|
|
||||||
image: asset.image,
|
|
||||||
tags: asset.tags,
|
|
||||||
version: asset.version,
|
|
||||||
background_image: asset.background_image,
|
|
||||||
background_video: asset.background_video,
|
|
||||||
payload: asset.payload,
|
|
||||||
type: asset.type
|
|
||||||
};
|
|
||||||
return acc;
|
|
||||||
}, {});
|
|
||||||
return reply.send({ assets, pages: Math.ceil(totalItems / 20) });
|
|
||||||
} catch (error) {
|
|
||||||
return reply.status(500).send({ error: "An error occured" });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
type PackageReq = FastifyRequest<{ Params: { package: string } }>;
|
|
||||||
app.get("/api/packages/:package", async (request: PackageReq, reply) => {
|
|
||||||
try {
|
|
||||||
const packageRow = await catalogAssets.findOne({
|
|
||||||
where: { package_name: request.params.package }
|
|
||||||
});
|
|
||||||
if (!packageRow) return reply.status(404).send({ error: "Package not found!" });
|
|
||||||
const details = {
|
|
||||||
title: packageRow.get("title"),
|
|
||||||
description: packageRow.get("description"),
|
|
||||||
image: packageRow.get("image"),
|
|
||||||
author: packageRow.get("author"),
|
|
||||||
tags: packageRow.get("tags"),
|
|
||||||
version: packageRow.get("version"),
|
|
||||||
background_image: packageRow.get("background_image"),
|
|
||||||
background_video: packageRow.get("background_video"),
|
|
||||||
payload: packageRow.get("payload"),
|
|
||||||
type: packageRow.get("type")
|
|
||||||
};
|
|
||||||
reply.send(details);
|
|
||||||
} catch (error) {
|
|
||||||
reply.status(500).send({ error: "An unexpected error occured" });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
type UploadReq = FastifyRequest<{ Headers: { psk: string; packagename: string } }>;
|
|
||||||
type CreateReq = FastifyRequest<{
|
|
||||||
Headers: { psk: string };
|
|
||||||
Body: {
|
|
||||||
uuid: string;
|
|
||||||
title: string;
|
|
||||||
image: string;
|
|
||||||
author: string;
|
|
||||||
version: string;
|
|
||||||
description: string;
|
|
||||||
tags: object | any;
|
|
||||||
payload: string;
|
|
||||||
background_video: string;
|
|
||||||
background_image: string;
|
|
||||||
type: CatalogType;
|
|
||||||
};
|
|
||||||
}>;
|
|
||||||
interface VerifyStatus {
|
|
||||||
status: number;
|
|
||||||
error?: Error;
|
|
||||||
}
|
|
||||||
async function verifyReq(
|
|
||||||
request: UploadReq | CreateReq,
|
|
||||||
upload: Boolean,
|
|
||||||
data: any
|
|
||||||
): Promise<VerifyStatus> {
|
|
||||||
if (parsedDoc.marketplace.enabled === false) {
|
|
||||||
return { status: 500, error: new Error("Marketplace Is disabled!") };
|
|
||||||
} else if (request.headers.psk !== parsedDoc.marketplace.psk) {
|
|
||||||
return { status: 403, error: new Error("PSK isn't correct!") };
|
|
||||||
} else if (upload && !request.headers.packagename) {
|
|
||||||
return { status: 500, error: new Error("No packagename defined!") };
|
|
||||||
} else if (upload && !data) {
|
|
||||||
return { status: 400, error: new Error("No file uploaded!") };
|
|
||||||
} else {
|
|
||||||
return { status: 200 };
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
app.post("/api/upload-asset", async (request: UploadReq, reply) => {
|
|
||||||
const data = await request.file();
|
|
||||||
const verify: VerifyStatus = await verifyReq(request, true, data);
|
|
||||||
if (verify.error !== undefined) {
|
|
||||||
reply.status(verify.status).send({ status: verify.error.message });
|
|
||||||
} else {
|
|
||||||
try {
|
|
||||||
await pipeline(
|
|
||||||
data.file,
|
|
||||||
createWriteStream(
|
|
||||||
fileURLToPath(
|
|
||||||
new URL(
|
|
||||||
`../database_assets/${request.headers.packagename}/${data.filename}`,
|
|
||||||
import.meta.url
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
return reply
|
|
||||||
.status(500)
|
|
||||||
.send({ status: `File couldn't be uploaded! (Package most likely doesn't exist)` });
|
|
||||||
}
|
|
||||||
return reply.status(verify.status).send({ status: "File uploaded successfully!" });
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.post("/api/create-package", async (request: CreateReq, reply) => {
|
|
||||||
const verify: VerifyStatus = await verifyReq(request, false, undefined);
|
|
||||||
if (verify.error !== undefined) {
|
|
||||||
reply.status(verify.status).send({ status: verify.error.message });
|
|
||||||
} else {
|
|
||||||
const body: Catalog = {
|
|
||||||
package_name: request.body.uuid,
|
|
||||||
title: request.body.title,
|
|
||||||
image: request.body.image,
|
|
||||||
author: request.body.author,
|
|
||||||
version: request.body.version,
|
|
||||||
description: request.body.description,
|
|
||||||
tags: request.body.tags,
|
|
||||||
payload: request.body.payload,
|
|
||||||
background_video: request.body.background_video,
|
|
||||||
background_image: request.body.background_image,
|
|
||||||
type: request.body.type as CatalogType
|
|
||||||
};
|
|
||||||
await catalogAssets.create({
|
|
||||||
package_name: body.package_name,
|
|
||||||
title: body.title,
|
|
||||||
image: body.image,
|
|
||||||
author: body.author,
|
|
||||||
version: body.version,
|
|
||||||
description: body.description,
|
|
||||||
tags: body.tags,
|
|
||||||
payload: body.payload,
|
|
||||||
background_video: body.background_video,
|
|
||||||
background_image: body.background_image,
|
|
||||||
type: body.type
|
|
||||||
});
|
|
||||||
const assets = fileURLToPath(new URL("../database_assets", import.meta.url));
|
|
||||||
try {
|
|
||||||
await access(`${assets}/${body.package_name}/`, constants.F_OK);
|
|
||||||
return reply.status(500).send({ status: "Package already exists!" });
|
|
||||||
} catch (err) {
|
|
||||||
await mkdir(`${assets}/${body.package_name}/`);
|
|
||||||
return reply.status(verify.status).send({ status: "Package created successfully!" });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
app.use(ssrHandler);
|
app.use(ssrHandler);
|
||||||
|
|
||||||
const port: number =
|
const port: number =
|
||||||
|
|
@ -282,8 +74,8 @@ app.listen({ port: port, host: "0.0.0.0" }).then(async () => {
|
||||||
`Server also listening on ${chalk.hex("#eb6f92").bold("http://0.0.0.0:" + port + "/")}`
|
`Server also listening on ${chalk.hex("#eb6f92").bold("http://0.0.0.0:" + port + "/")}`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
if (parsedDoc.marketplace.enabled) {
|
||||||
await catalogAssets.sync();
|
await catalogAssets.sync();
|
||||||
await setupDB(catalogAssets);
|
await setupDB(catalogAssets);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
export { CatalogModel, Catalog };
|
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ import { getLangFromUrl, useTranslations } from "../i18n/utils";
|
||||||
import { isMobileNavOpen } from "../store.js";
|
import { isMobileNavOpen } from "../store.js";
|
||||||
import HeaderButton from "./HeaderButton.astro";
|
import HeaderButton from "./HeaderButton.astro";
|
||||||
import Logo from "./Logo.astro";
|
import Logo from "./Logo.astro";
|
||||||
|
import { MARKETPLACE_ENABLED } from "astro:env/client";
|
||||||
const lang = getLangFromUrl(Astro.url);
|
const lang = getLangFromUrl(Astro.url);
|
||||||
const t = useTranslations(lang);
|
const t = useTranslations(lang);
|
||||||
---
|
---
|
||||||
|
|
@ -40,12 +40,14 @@ const t = useTranslations(lang);
|
||||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||||
/>
|
/>
|
||||||
</HeaderButton>
|
</HeaderButton>
|
||||||
|
{MARKETPLACE_ENABLED &&
|
||||||
<HeaderButton text={t("header.catalog")} route={`/catalog/1`}>
|
<HeaderButton text={t("header.catalog")} route={`/catalog/1`}>
|
||||||
<Icon
|
<Icon
|
||||||
name="ph:shopping-bag-open-fill"
|
name="ph:shopping-bag-open-fill"
|
||||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||||
/>
|
/>
|
||||||
</HeaderButton>
|
</HeaderButton>
|
||||||
|
}
|
||||||
<HeaderButton text={t("header.morelinks")}>
|
<HeaderButton text={t("header.morelinks")}>
|
||||||
<Icon
|
<Icon
|
||||||
name="ph:link-bold"
|
name="ph:link-bold"
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import { getLangFromUrl, useTranslations } from "../i18n/utils";
|
||||||
import HeaderButton from "./HeaderButton.astro";
|
import HeaderButton from "./HeaderButton.astro";
|
||||||
const lang = getLangFromUrl(Astro.url);
|
const lang = getLangFromUrl(Astro.url);
|
||||||
const t = useTranslations(lang);
|
const t = useTranslations(lang);
|
||||||
|
import { MARKETPLACE_ENABLED } from "astro:env/client";
|
||||||
---
|
---
|
||||||
|
|
||||||
<div
|
<div
|
||||||
|
|
@ -31,12 +32,14 @@ const t = useTranslations(lang);
|
||||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||||
/>
|
/>
|
||||||
</HeaderButton>
|
</HeaderButton>
|
||||||
|
{MARKETPLACE_ENABLED &&
|
||||||
<HeaderButton text={t("header.catalog")} route={`/catalog/1`}>
|
<HeaderButton text={t("header.catalog")} route={`/catalog/1`}>
|
||||||
<Icon
|
<Icon
|
||||||
name="ph:shopping-bag-open-fill"
|
name="ph:shopping-bag-open-fill"
|
||||||
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
class="h-6 w-6 text-text-color transition duration-500 group-hover:text-text-hover-color md:h-6 md:w-6"
|
||||||
/>
|
/>
|
||||||
</HeaderButton>
|
</HeaderButton>
|
||||||
|
}
|
||||||
<HeaderButton text={t("header.morelinks")}>
|
<HeaderButton text={t("header.morelinks")}>
|
||||||
<Icon
|
<Icon
|
||||||
name="ph:link-bold"
|
name="ph:link-bold"
|
||||||
|
|
|
||||||
|
|
@ -50,7 +50,7 @@ let compRef = [];
|
||||||
<path fill="currentColor" d="M216 48h-40v-8a24 24 0 0 0-24-24h-48a24 24 0 0 0-24 24v8H40a8 8 0 0 0 0 16h8v144a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16V64h8a8 8 0 0 0 0-16M112 168a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm48 0a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm0-120H96v-8a8 8 0 0 1 8-8h48a8 8 0 0 1 8 8Z" />
|
<path fill="currentColor" d="M216 48h-40v-8a24 24 0 0 0-24-24h-48a24 24 0 0 0-24 24v8H40a8 8 0 0 0 0 16h8v144a16 16 0 0 0 16 16h128a16 16 0 0 0 16-16V64h8a8 8 0 0 0 0-16M112 168a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm48 0a8 8 0 0 1-16 0v-64a8 8 0 0 1 16 0Zm0-120H96v-8a8 8 0 0 1 8-8h48a8 8 0 0 1 8 8Z" />
|
||||||
</svg>
|
</svg>
|
||||||
</div>
|
</div>
|
||||||
<a class="h-8 w-8 cursor-pointer" href={"/catalog/" + asset.package_name}>
|
<a class="h-8 w-8 cursor-pointer" href={`/catalog/package/${asset.package_name}`}>
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256" {...$$props}>
|
<svg xmlns="http://www.w3.org/2000/svg" width="100%" height="100%" viewBox="0 0 256 256" {...$$props}>
|
||||||
<path fill="currentColor" d="M192 136v72a16 16 0 0 1-16 16H48a16 16 0 0 1-16-16V80a16 16 0 0 1 16-16h72a8 8 0 0 1 0 16H48v128h128v-72a8 8 0 0 1 16 0m32-96a8 8 0 0 0-8-8h-64a8 8 0 0 0-5.66 13.66L172.69 72l-42.35 42.34a8 8 0 0 0 11.32 11.32L184 83.31l26.34 26.35A8 8 0 0 0 224 104Z" />
|
<path fill="currentColor" d="M192 136v72a16 16 0 0 1-16 16H48a16 16 0 0 1-16-16V80a16 16 0 0 1 16-16h72a8 8 0 0 1 0 16H48v128h128v-72a8 8 0 0 1 16 0m32-96a8 8 0 0 0-8-8h-64a8 8 0 0 0-5.66 13.66L172.69 72l-42.35 42.34a8 8 0 0 0 11.32 11.32L184 83.31l26.34 26.35A8 8 0 0 0 224 104Z" />
|
||||||
</svg>
|
</svg>
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ export function getStaticPaths() {
|
||||||
return STATIC_PATHS;
|
return STATIC_PATHS;
|
||||||
}
|
}
|
||||||
export const prerender = true;
|
export const prerender = true;
|
||||||
|
import { MARKETPLACE_ENABLED } from "astro:env/client";
|
||||||
---
|
---
|
||||||
|
|
||||||
<Layout title="Settings">
|
<Layout title="Settings">
|
||||||
|
|
@ -22,6 +23,7 @@ export const prerender = true;
|
||||||
<div class="flex flex-row flex-wrap gap-4 items-center roboto">
|
<div class="flex flex-row flex-wrap gap-4 items-center roboto">
|
||||||
<div class="justify-center flex flex-row gap-6 flex-wrap md:justify-normal">
|
<div class="justify-center flex flex-row gap-6 flex-wrap md:justify-normal">
|
||||||
<InstalledThemes client:only="svelte" />
|
<InstalledThemes client:only="svelte" />
|
||||||
|
{MARKETPLACE_ENABLED &&
|
||||||
<a href="/catalog/1" class="rounded-3xl bg-navbar-color w-64 flex flex-col">
|
<a href="/catalog/1" class="rounded-3xl bg-navbar-color w-64 flex flex-col">
|
||||||
<div class="w-full items-center justify-center flex aspect-[16/9]">
|
<div class="w-full items-center justify-center flex aspect-[16/9]">
|
||||||
<Icon name="ph:plus-bold" class="h-16 w-16" />
|
<Icon name="ph:plus-bold" class="h-16 w-16" />
|
||||||
|
|
@ -30,6 +32,7 @@ export const prerender = true;
|
||||||
Get more themes in the <strong>Nebula Catalog!</strong>
|
Get more themes in the <strong>Nebula Catalog!</strong>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="text-3xl roboto font-bold text-text-color"></div>
|
<div class="text-3xl roboto font-bold text-text-color"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue