mirror of
https://github.com/NebulaServices/Nebula.git
synced 2025-05-13 03:50:02 -04:00
.
This commit is contained in:
parent
a62534618b
commit
06d02c42f1
18 changed files with 9778 additions and 0 deletions
12
.astro/astro/env.d.ts
vendored
Normal file
12
.astro/astro/env.d.ts
vendored
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
declare module 'astro:env/client' {
|
||||||
|
export const VERSION: string;
|
||||||
|
export const MARKETPLACE_ENABLED: boolean;
|
||||||
|
export const SEO: string;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'astro:env/server' {
|
||||||
|
|
||||||
|
|
||||||
|
export const getSecret: (key: string) => string | undefined;
|
||||||
|
}
|
1
.astro/content-assets.mjs
Normal file
1
.astro/content-assets.mjs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export default new Map();
|
1
.astro/content-modules.mjs
Normal file
1
.astro/content-modules.mjs
Normal file
|
@ -0,0 +1 @@
|
||||||
|
export default new Map();
|
155
.astro/content.d.ts
vendored
Normal file
155
.astro/content.d.ts
vendored
Normal file
|
@ -0,0 +1,155 @@
|
||||||
|
declare module 'astro:content' {
|
||||||
|
export interface RenderResult {
|
||||||
|
Content: import('astro/runtime/server/index.js').AstroComponentFactory;
|
||||||
|
headings: import('astro').MarkdownHeading[];
|
||||||
|
remarkPluginFrontmatter: Record<string, any>;
|
||||||
|
}
|
||||||
|
interface Render {
|
||||||
|
'.md': Promise<RenderResult>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface RenderedContent {
|
||||||
|
html: string;
|
||||||
|
metadata?: {
|
||||||
|
imagePaths: Array<string>;
|
||||||
|
[key: string]: unknown;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare module 'astro:content' {
|
||||||
|
type Flatten<T> = T extends { [K: string]: infer U } ? U : never;
|
||||||
|
|
||||||
|
export type CollectionKey = keyof AnyEntryMap;
|
||||||
|
export type CollectionEntry<C extends CollectionKey> = Flatten<AnyEntryMap[C]>;
|
||||||
|
|
||||||
|
export type ContentCollectionKey = keyof ContentEntryMap;
|
||||||
|
export type DataCollectionKey = keyof DataEntryMap;
|
||||||
|
|
||||||
|
type AllValuesOf<T> = T extends any ? T[keyof T] : never;
|
||||||
|
type ValidContentEntrySlug<C extends keyof ContentEntryMap> = AllValuesOf<
|
||||||
|
ContentEntryMap[C]
|
||||||
|
>['slug'];
|
||||||
|
|
||||||
|
/** @deprecated Use `getEntry` instead. */
|
||||||
|
export function getEntryBySlug<
|
||||||
|
C extends keyof ContentEntryMap,
|
||||||
|
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||||
|
>(
|
||||||
|
collection: C,
|
||||||
|
// Note that this has to accept a regular string too, for SSR
|
||||||
|
entrySlug: E,
|
||||||
|
): E extends ValidContentEntrySlug<C>
|
||||||
|
? Promise<CollectionEntry<C>>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
|
||||||
|
/** @deprecated Use `getEntry` instead. */
|
||||||
|
export function getDataEntryById<C extends keyof DataEntryMap, E extends keyof DataEntryMap[C]>(
|
||||||
|
collection: C,
|
||||||
|
entryId: E,
|
||||||
|
): Promise<CollectionEntry<C>>;
|
||||||
|
|
||||||
|
export function getCollection<C extends keyof AnyEntryMap, E extends CollectionEntry<C>>(
|
||||||
|
collection: C,
|
||||||
|
filter?: (entry: CollectionEntry<C>) => entry is E,
|
||||||
|
): Promise<E[]>;
|
||||||
|
export function getCollection<C extends keyof AnyEntryMap>(
|
||||||
|
collection: C,
|
||||||
|
filter?: (entry: CollectionEntry<C>) => unknown,
|
||||||
|
): Promise<CollectionEntry<C>[]>;
|
||||||
|
|
||||||
|
export function getEntry<
|
||||||
|
C extends keyof ContentEntryMap,
|
||||||
|
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||||
|
>(entry: {
|
||||||
|
collection: C;
|
||||||
|
slug: E;
|
||||||
|
}): E extends ValidContentEntrySlug<C>
|
||||||
|
? Promise<CollectionEntry<C>>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
export function getEntry<
|
||||||
|
C extends keyof DataEntryMap,
|
||||||
|
E extends keyof DataEntryMap[C] | (string & {}),
|
||||||
|
>(entry: {
|
||||||
|
collection: C;
|
||||||
|
id: E;
|
||||||
|
}): E extends keyof DataEntryMap[C]
|
||||||
|
? Promise<DataEntryMap[C][E]>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
export function getEntry<
|
||||||
|
C extends keyof ContentEntryMap,
|
||||||
|
E extends ValidContentEntrySlug<C> | (string & {}),
|
||||||
|
>(
|
||||||
|
collection: C,
|
||||||
|
slug: E,
|
||||||
|
): E extends ValidContentEntrySlug<C>
|
||||||
|
? Promise<CollectionEntry<C>>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
export function getEntry<
|
||||||
|
C extends keyof DataEntryMap,
|
||||||
|
E extends keyof DataEntryMap[C] | (string & {}),
|
||||||
|
>(
|
||||||
|
collection: C,
|
||||||
|
id: E,
|
||||||
|
): E extends keyof DataEntryMap[C]
|
||||||
|
? string extends keyof DataEntryMap[C]
|
||||||
|
? Promise<DataEntryMap[C][E]> | undefined
|
||||||
|
: Promise<DataEntryMap[C][E]>
|
||||||
|
: Promise<CollectionEntry<C> | undefined>;
|
||||||
|
|
||||||
|
/** Resolve an array of entry references from the same collection */
|
||||||
|
export function getEntries<C extends keyof ContentEntryMap>(
|
||||||
|
entries: {
|
||||||
|
collection: C;
|
||||||
|
slug: ValidContentEntrySlug<C>;
|
||||||
|
}[],
|
||||||
|
): Promise<CollectionEntry<C>[]>;
|
||||||
|
export function getEntries<C extends keyof DataEntryMap>(
|
||||||
|
entries: {
|
||||||
|
collection: C;
|
||||||
|
id: keyof DataEntryMap[C];
|
||||||
|
}[],
|
||||||
|
): Promise<CollectionEntry<C>[]>;
|
||||||
|
|
||||||
|
export function render<C extends keyof AnyEntryMap>(
|
||||||
|
entry: AnyEntryMap[C][string],
|
||||||
|
): Promise<RenderResult>;
|
||||||
|
|
||||||
|
export function reference<C extends keyof AnyEntryMap>(
|
||||||
|
collection: C,
|
||||||
|
): import('astro/zod').ZodEffects<
|
||||||
|
import('astro/zod').ZodString,
|
||||||
|
C extends keyof ContentEntryMap
|
||||||
|
? {
|
||||||
|
collection: C;
|
||||||
|
slug: ValidContentEntrySlug<C>;
|
||||||
|
}
|
||||||
|
: {
|
||||||
|
collection: C;
|
||||||
|
id: keyof DataEntryMap[C];
|
||||||
|
}
|
||||||
|
>;
|
||||||
|
// Allow generic `string` to avoid excessive type errors in the config
|
||||||
|
// if `dev` is not running to update as you edit.
|
||||||
|
// Invalid collection names will be caught at build time.
|
||||||
|
export function reference<C extends string>(
|
||||||
|
collection: C,
|
||||||
|
): import('astro/zod').ZodEffects<import('astro/zod').ZodString, never>;
|
||||||
|
|
||||||
|
type ReturnTypeOrOriginal<T> = T extends (...args: any[]) => infer R ? R : T;
|
||||||
|
type InferEntrySchema<C extends keyof AnyEntryMap> = import('astro/zod').infer<
|
||||||
|
ReturnTypeOrOriginal<Required<ContentConfig['collections'][C]>['schema']>
|
||||||
|
>;
|
||||||
|
|
||||||
|
type ContentEntryMap = {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
type DataEntryMap = {
|
||||||
|
|
||||||
|
};
|
||||||
|
|
||||||
|
type AnyEntryMap = ContentEntryMap & DataEntryMap;
|
||||||
|
|
||||||
|
export type ContentConfig = typeof import("../src/content.config.mjs");
|
||||||
|
}
|
1
.astro/data-store.json
Normal file
1
.astro/data-store.json
Normal file
|
@ -0,0 +1 @@
|
||||||
|
[["Map",1,2],"meta::meta",["Map",3,4,5,6],"astro-version","5.1.2","astro-config-digest","{\"root\":{},\"srcDir\":{},\"publicDir\":{},\"outDir\":{},\"cacheDir\":{},\"site\":\"http://localhost:4321\",\"compressHTML\":true,\"base\":\"/\",\"trailingSlash\":\"ignore\",\"output\":\"server\",\"scopedStyleStrategy\":\"attribute\",\"build\":{\"format\":\"directory\",\"client\":{},\"server\":{},\"assets\":\"_astro\",\"serverEntry\":\"entry.mjs\",\"redirects\":true,\"inlineStylesheets\":\"auto\",\"concurrency\":1},\"server\":{\"open\":false,\"host\":\"0.0.0.0\",\"port\":4321,\"streaming\":true},\"redirects\":{},\"image\":{\"endpoint\":{\"route\":\"/_image\"},\"service\":{\"entrypoint\":\"astro/assets/services/sharp\",\"config\":{}},\"domains\":[],\"remotePatterns\":[]},\"devToolbar\":{\"enabled\":true},\"markdown\":{\"syntaxHighlight\":\"shiki\",\"shikiConfig\":{\"langs\":[],\"langAlias\":{},\"theme\":\"github-dark\",\"themes\":{},\"wrap\":false,\"transformers\":[]},\"remarkPlugins\":[],\"rehypePlugins\":[],\"remarkRehype\":{},\"gfm\":true,\"smartypants\":true},\"security\":{\"checkOrigin\":true},\"env\":{\"schema\":{\"VERSION\":{\"context\":\"client\",\"access\":\"public\",\"type\":\"string\",\"optional\":true,\"default\":\"9.0.4\"},\"MARKETPLACE_ENABLED\":{\"context\":\"client\",\"access\":\"public\",\"type\":\"boolean\",\"optional\":true,\"default\":true},\"SEO\":{\"context\":\"client\",\"access\":\"public\",\"type\":\"string\",\"optional\":true,\"default\":\"{\\\"enabled\\\":true,\\\"domain\\\":\\\"localhost:4321\\\"}\"}},\"validateSecrets\":false},\"experimental\":{\"clientPrerender\":false,\"contentIntellisense\":false,\"responsiveImages\":false},\"legacy\":{\"collections\":false}}"]
|
5
.astro/env.d.ts
vendored
Normal file
5
.astro/env.d.ts
vendored
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
declare module 'astro:env/client' {
|
||||||
|
export const VERSION: string;
|
||||||
|
export const MARKETPLACE_ENABLED: boolean;
|
||||||
|
export const SEO: string;
|
||||||
|
}
|
9167
.astro/icon.d.ts
vendored
Normal file
9167
.astro/icon.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load diff
5
.astro/settings.json
Normal file
5
.astro/settings.json
Normal file
|
@ -0,0 +1,5 @@
|
||||||
|
{
|
||||||
|
"_variables": {
|
||||||
|
"lastUpdateCheck": 1736496310589
|
||||||
|
}
|
||||||
|
}
|
3
.astro/types.d.ts
vendored
Normal file
3
.astro/types.d.ts
vendored
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
/// <reference types="astro/client" />
|
||||||
|
/// <reference path="content.d.ts" />
|
||||||
|
/// <reference path="env.d.ts" />
|
23
config.toml
Normal file
23
config.toml
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
[marketplace]
|
||||||
|
enabled = true # Turn on or off the marketplace entirely
|
||||||
|
psk = "CHANGEME" # Change this to something more secure.
|
||||||
|
level = 1
|
||||||
|
|
||||||
|
[db]
|
||||||
|
name = "database" # Your databsae name
|
||||||
|
username = "username" # The username of your DB (SQLITE just ignores this)
|
||||||
|
password = "password" # The password to your DB (SQLITE ignores this)
|
||||||
|
postgres = false # Enable to use postgres over sqlite (recommended for large prod instances)
|
||||||
|
|
||||||
|
[postgres] # Set the "domain" to either and ip address or a actual domain
|
||||||
|
domain = ""
|
||||||
|
port = 5432
|
||||||
|
|
||||||
|
[seo]
|
||||||
|
enabled = true # Disabled by default as you probably don't want it
|
||||||
|
domain = "http://localhost:4321" # Set to YOUR domain. Make sure to include http or https://
|
||||||
|
|
||||||
|
[server.server]
|
||||||
|
port = 8080
|
||||||
|
wisp = true
|
||||||
|
logging = false # Disable for the tons & tons of logs to go away (useful for debugging but otherwise eh)
|
BIN
database.sqlite
Normal file
BIN
database.sqlite
Normal file
Binary file not shown.
BIN
database_assets/com.nebula.gruvbox/gruvbox.jpg
Normal file
BIN
database_assets/com.nebula.gruvbox/gruvbox.jpg
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.6 KiB |
0
database_assets/gyatt/gyatt.js
Normal file
0
database_assets/gyatt/gyatt.js
Normal file
55
server/config.js
Normal file
55
server/config.js
Normal file
|
@ -0,0 +1,55 @@
|
||||||
|
import { readFileSync } from "node:fs";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import chalk from "chalk";
|
||||||
|
import { parse } from "smol-toml";
|
||||||
|
let doc = readFileSync(fileURLToPath(new URL("../config.toml", import.meta.url))).toString();
|
||||||
|
const parsedDoc = parse(doc);
|
||||||
|
function verify(t) {
|
||||||
|
for (let i = 0; i !== t.length; i++) {
|
||||||
|
if (typeof t[i].typeOF !== t[i].type) {
|
||||||
|
throw new Error(`Invalid structure: "${t[i].name}" should be a(n) ${t[i].type}`);
|
||||||
|
}
|
||||||
|
if (t[i].verifyExtras) {
|
||||||
|
const extra = t[i].verifyExtras();
|
||||||
|
if (extra !== true) {
|
||||||
|
throw extra;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
verify([
|
||||||
|
{ name: "marketplace", typeOF: parsedDoc.marketplace, type: "object" },
|
||||||
|
{ name: "marketplace.enabled", typeOF: parsedDoc.marketplace.enabled, type: "boolean" },
|
||||||
|
{ name: "marketplace.psk", typeOF: parsedDoc.marketplace.psk, type: "string" },
|
||||||
|
{ name: "server", typeOF: parsedDoc.server, type: "object" },
|
||||||
|
{ name: "server.server", typeOF: parsedDoc.server.server, type: "object" },
|
||||||
|
{ name: "server.server.port", typeOF: parsedDoc.server.server.port, type: "number" },
|
||||||
|
{ name: "server.server.wisp", typeOF: parsedDoc.server.server.wisp, type: "boolean" },
|
||||||
|
{ name: "server.server.logging", typeOF: parsedDoc.server.server.logging, type: "boolean" },
|
||||||
|
{ name: "seo", typeOF: parsedDoc.seo, type: "object" },
|
||||||
|
{ name: "seo.enabled", typeOF: parsedDoc.seo.enabled, type: "boolean" },
|
||||||
|
{ name: "seo.domain", typeOF: parsedDoc.seo.domain, type: "string", verifyExtras: () => {
|
||||||
|
try {
|
||||||
|
new URL(parsedDoc.seo.domain);
|
||||||
|
}
|
||||||
|
catch (e) {
|
||||||
|
return Error(e);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} },
|
||||||
|
{ name: "db", typeOF: parsedDoc.db, type: "object" },
|
||||||
|
{ name: "db.name", typeOF: parsedDoc.db.name, type: "string" },
|
||||||
|
{ name: "db.username", typeOF: parsedDoc.db.username, type: "string" },
|
||||||
|
{ name: "db.password", typeOF: parsedDoc.db.password, type: "string" },
|
||||||
|
{ name: "db.postgres", typeOF: parsedDoc.db.postgres, type: "boolean" },
|
||||||
|
{ name: "postgres", typeOF: parsedDoc.postgres, type: "object" },
|
||||||
|
{ name: "postgres.domain", typeOF: parsedDoc.postgres.domain, type: "string" },
|
||||||
|
{ name: "postgres.port", typeOF: parsedDoc.postgres.port, type: "number" }
|
||||||
|
]);
|
||||||
|
if (parsedDoc.marketplace.psk === "CHANGEME") {
|
||||||
|
console.warn(chalk.yellow.bold('PSK should be changed from "CHANGEME"'));
|
||||||
|
}
|
||||||
|
if (parsedDoc.db.password === "password") {
|
||||||
|
console.warn(chalk.red.bold("You should change your DB password!!"));
|
||||||
|
}
|
||||||
|
export { parsedDoc };
|
87
server/dbSetup.js
Normal file
87
server/dbSetup.js
Normal file
|
@ -0,0 +1,87 @@
|
||||||
|
import chalk from "chalk";
|
||||||
|
import ora from "ora";
|
||||||
|
async function installItems(db, items) {
|
||||||
|
items.forEach(async (item) => {
|
||||||
|
await db.create({
|
||||||
|
package_name: item.package_name,
|
||||||
|
title: item.title,
|
||||||
|
image: item.image,
|
||||||
|
author: item.author,
|
||||||
|
version: item.version,
|
||||||
|
description: item.description,
|
||||||
|
tags: item.tags,
|
||||||
|
payload: item.payload,
|
||||||
|
background_video: item.background_video,
|
||||||
|
background_image: item.background_image,
|
||||||
|
type: item.type
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
async function setupDB(db) {
|
||||||
|
//We have some packages that need to be installed if they aren't.
|
||||||
|
const items = [
|
||||||
|
{
|
||||||
|
package_name: "com.nebula.gruvbox",
|
||||||
|
title: "Gruvbox",
|
||||||
|
image: "gruvbox.jpg",
|
||||||
|
author: "Nebula Services",
|
||||||
|
version: "1.0.0",
|
||||||
|
description: "The gruvbox theme",
|
||||||
|
tags: ["Theme", "Simple"],
|
||||||
|
payload: "gruvbox.css",
|
||||||
|
type: "theme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
package_name: "com.nebula.oled",
|
||||||
|
title: "Oled theme",
|
||||||
|
image: "oled.jpg",
|
||||||
|
author: "Nebula Services",
|
||||||
|
version: "1.0.0",
|
||||||
|
description: "A sleek & simple Oled theme for Nebula",
|
||||||
|
tags: ["Theme", "Simple", "Sleek"],
|
||||||
|
payload: "oled.css",
|
||||||
|
type: "theme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
package_name: "com.nebula.lightTheme",
|
||||||
|
title: "Light Theme",
|
||||||
|
image: "light.png",
|
||||||
|
author: "Nebula Services",
|
||||||
|
version: "1.0.0",
|
||||||
|
description: "A sleek light theme for Nebula",
|
||||||
|
tags: ["Theme", "Simple", "Light"],
|
||||||
|
payload: "light.css",
|
||||||
|
type: "theme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
package_name: "com.nebula.retro",
|
||||||
|
title: "Retro Theme",
|
||||||
|
image: "retro.png",
|
||||||
|
author: "Nebula Services",
|
||||||
|
version: "1.0.0",
|
||||||
|
description: "Give a retro look to Nebula",
|
||||||
|
tags: ["Theme", "Simple", "Dark", "Retro"],
|
||||||
|
payload: "retro.css",
|
||||||
|
type: "theme"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
package_name: 'gyatt',
|
||||||
|
title: 'gyatt',
|
||||||
|
image: 'gyatt',
|
||||||
|
author: "nebuka",
|
||||||
|
version: "2",
|
||||||
|
description: "e",
|
||||||
|
tags: ["e"],
|
||||||
|
payload: "gyatt.js",
|
||||||
|
type: "plugin-page"
|
||||||
|
}
|
||||||
|
//To add plugins: plugin types consist of plugin-sw (workerware) & plugin-page (uv.config.inject)
|
||||||
|
];
|
||||||
|
const dbItems = await db.findAll();
|
||||||
|
if (dbItems.length === 0) {
|
||||||
|
const spinner = ora(chalk.hex("#7967dd")("Performing DB setup...")).start();
|
||||||
|
await installItems(db, items);
|
||||||
|
spinner.succeed(chalk.hex("#eb6f92")("DB setup complete!"));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export { setupDB };
|
169
server/marketplace.js
Normal file
169
server/marketplace.js
Normal file
|
@ -0,0 +1,169 @@
|
||||||
|
import { createWriteStream } from "node:fs";
|
||||||
|
import { constants, access, mkdir } from "node:fs/promises";
|
||||||
|
import { pipeline } from "node:stream/promises";
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import { DataTypes, Sequelize } from "sequelize";
|
||||||
|
import { parsedDoc } from "./config.js";
|
||||||
|
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
|
||||||
|
});
|
||||||
|
const catalogAssets = db.define("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) {
|
||||||
|
app.get("/api/catalog-stats/", (request, reply) => {
|
||||||
|
reply.send({
|
||||||
|
version: "1.0.0",
|
||||||
|
spec: "Nebula Services",
|
||||||
|
enabled: true
|
||||||
|
});
|
||||||
|
});
|
||||||
|
app.get("/api/catalog-assets/", async (request, reply) => {
|
||||||
|
try {
|
||||||
|
const { page } = request.query;
|
||||||
|
const pageNum = 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" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
app.get("/api/packages/:package", async (request, 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" });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
async function verifyReq(request, upload, data) {
|
||||||
|
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, reply) => {
|
||||||
|
const data = await request.file();
|
||||||
|
const verify = 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, reply) => {
|
||||||
|
const verify = await verifyReq(request, false, undefined);
|
||||||
|
if (verify.error !== undefined) {
|
||||||
|
reply.status(verify.status).send({ status: verify.error.message });
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
const body = {
|
||||||
|
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
|
||||||
|
};
|
||||||
|
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 };
|
71
server/server.js
Normal file
71
server/server.js
Normal file
|
@ -0,0 +1,71 @@
|
||||||
|
import { fileURLToPath } from "node:url";
|
||||||
|
import fastifyCompress from "@fastify/compress";
|
||||||
|
import fastifyHelmet from "@fastify/helmet";
|
||||||
|
import fastifyMiddie from "@fastify/middie";
|
||||||
|
import fastifyMultipart from "@fastify/multipart";
|
||||||
|
import fastifyStatic from "@fastify/static";
|
||||||
|
import chalk from "chalk";
|
||||||
|
import Fastify from "fastify";
|
||||||
|
import gradient from "gradient-string";
|
||||||
|
//@ts-ignore WHY would I want this typechecked AT ALL
|
||||||
|
import { handler as ssrHandler } from "../dist/server/entry.mjs";
|
||||||
|
import { parsedDoc } from "./config.js";
|
||||||
|
import { setupDB } from "./dbSetup.js";
|
||||||
|
import { catalogAssets, marketplaceAPI } from "./marketplace.js";
|
||||||
|
import { serverFactory } from "./serverFactory.js";
|
||||||
|
const app = Fastify({
|
||||||
|
logger: parsedDoc.server.server.logging,
|
||||||
|
ignoreDuplicateSlashes: true,
|
||||||
|
ignoreTrailingSlash: true,
|
||||||
|
serverFactory: serverFactory
|
||||||
|
});
|
||||||
|
await app.register(fastifyCompress, {
|
||||||
|
encodings: ["br", "gzip", "deflate"]
|
||||||
|
});
|
||||||
|
await app.register(fastifyMultipart, {
|
||||||
|
limits: {
|
||||||
|
fileSize: 25 * 1024 * 1024,
|
||||||
|
parts: Infinity
|
||||||
|
},
|
||||||
|
});
|
||||||
|
await app.register(fastifyHelmet, {
|
||||||
|
xPoweredBy: false,
|
||||||
|
crossOriginEmbedderPolicy: true,
|
||||||
|
crossOriginOpenerPolicy: true,
|
||||||
|
contentSecurityPolicy: false //Disabled because astro DOES NOT LIKE IT
|
||||||
|
});
|
||||||
|
await app.register(fastifyStatic, {
|
||||||
|
root: fileURLToPath(new URL("../dist/client", import.meta.url))
|
||||||
|
});
|
||||||
|
//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, {
|
||||||
|
root: fileURLToPath(new URL("../database_assets", import.meta.url)),
|
||||||
|
prefix: "/packages/",
|
||||||
|
decorateReply: false
|
||||||
|
});
|
||||||
|
marketplaceAPI(app);
|
||||||
|
}
|
||||||
|
await app.register(fastifyMiddie);
|
||||||
|
app.use(ssrHandler);
|
||||||
|
const port = parseInt(process.env.PORT) || parsedDoc.server.server.port || parseInt("8080");
|
||||||
|
const titleText = `
|
||||||
|
_ _ _ _ ____ _
|
||||||
|
| \\ | | ___| |__ _ _| | __ _ / ___| ___ _ ____ _(_) ___ ___ ___
|
||||||
|
| \\| |/ _ \\ '_ \\| | | | |/ _' | \\___ \\ / _ \\ '__\\ \\ / / |/ __/ _ \\/ __|
|
||||||
|
| |\\ | __/ |_) | |_| | | (_| | ___) | __/ | \\ V /| | (_| __/\\__ \\
|
||||||
|
|_| \\_|\\___|_.__/ \\__,_|_|\\__,_| |____/ \\___|_| \\_/ |_|\\___\\___||___/
|
||||||
|
`;
|
||||||
|
const titleColors = {
|
||||||
|
purple: "#7967dd",
|
||||||
|
pink: "#eb6f92"
|
||||||
|
};
|
||||||
|
console.log(gradient(Object.values(titleColors)).multiline(titleText));
|
||||||
|
app.listen({ port: port, host: "0.0.0.0" }).then(async () => {
|
||||||
|
console.log(chalk.hex("#7967dd")(`Server listening on ${chalk.hex("#eb6f92").bold("http://localhost:" + port + "/")}`));
|
||||||
|
console.log(chalk.hex("#7967dd")(`Server also listening on ${chalk.hex("#eb6f92").bold("http://0.0.0.0:" + port + "/")}`));
|
||||||
|
if (parsedDoc.marketplace.enabled) {
|
||||||
|
await catalogAssets.sync();
|
||||||
|
await setupDB(catalogAssets);
|
||||||
|
}
|
||||||
|
});
|
23
server/serverFactory.js
Normal file
23
server/serverFactory.js
Normal file
|
@ -0,0 +1,23 @@
|
||||||
|
import { createServer } from "node:http";
|
||||||
|
import wisp from "wisp-server-node";
|
||||||
|
import { LOG_LEVEL } from "wisp-server-node/dist/Types.js";
|
||||||
|
import { parsedDoc } from "./config.js";
|
||||||
|
const wispOptions = {
|
||||||
|
logLevel: parsedDoc.server.server.logging ? LOG_LEVEL.DEBUG : LOG_LEVEL.NONE,
|
||||||
|
pingInterval: 30
|
||||||
|
};
|
||||||
|
const serverFactory = (handler) => {
|
||||||
|
const httpServer = createServer();
|
||||||
|
httpServer.on("request", (req, res) => {
|
||||||
|
handler(req, res);
|
||||||
|
});
|
||||||
|
httpServer.on("upgrade", (req, socket, head) => {
|
||||||
|
if (parsedDoc.server.server.wisp) {
|
||||||
|
if (req.url?.endsWith("/wisp/")) {
|
||||||
|
wisp.routeRequest(req, socket, head, wispOptions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return httpServer;
|
||||||
|
};
|
||||||
|
export { serverFactory };
|
Loading…
Add table
Add a link
Reference in a new issue