create-weave-backend-app 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/README.md +11 -0
  2. package/dist/chunk-HIPWNEQ7.js +366 -0
  3. package/dist/create-app.d.ts +14 -0
  4. package/dist/create-app.js +6 -0
  5. package/dist/index.d.ts +1 -0
  6. package/dist/index.js +120 -0
  7. package/package.json +66 -0
  8. package/template/+express+azure-web-pubsub/README.md +38 -0
  9. package/template/+express+azure-web-pubsub/eslint.config.js +11 -0
  10. package/template/+express+azure-web-pubsub/example.env +10 -0
  11. package/template/+express+azure-web-pubsub/example.gitignore +37 -0
  12. package/template/+express+azure-web-pubsub/src/api/v1/controllers/delImage.ts +22 -0
  13. package/template/+express+azure-web-pubsub/src/api/v1/controllers/getHealth.ts +5 -0
  14. package/template/+express+azure-web-pubsub/src/api/v1/controllers/getImage.ts +35 -0
  15. package/template/+express+azure-web-pubsub/src/api/v1/controllers/getImages.ts +19 -0
  16. package/template/+express+azure-web-pubsub/src/api/v1/controllers/getRoomConnect.ts +17 -0
  17. package/template/+express+azure-web-pubsub/src/api/v1/controllers/postRemoveBackground.ts +60 -0
  18. package/template/+express+azure-web-pubsub/src/api/v1/controllers/postUploadImage.ts +35 -0
  19. package/template/+express+azure-web-pubsub/src/api/v1/router.ts +46 -0
  20. package/template/+express+azure-web-pubsub/src/app.ts +32 -0
  21. package/template/+express+azure-web-pubsub/src/config/config.ts +38 -0
  22. package/template/+express+azure-web-pubsub/src/constants.ts +1 -0
  23. package/template/+express+azure-web-pubsub/src/images/persistence.ts +240 -0
  24. package/template/+express+azure-web-pubsub/src/logger/logger.ts +24 -0
  25. package/template/+express+azure-web-pubsub/src/middlewares/body-parser.ts +11 -0
  26. package/template/+express+azure-web-pubsub/src/middlewares/cors.ts +16 -0
  27. package/template/+express+azure-web-pubsub/src/middlewares/http-logger.ts +13 -0
  28. package/template/+express+azure-web-pubsub/src/middlewares/http-response-headers.ts +14 -0
  29. package/template/+express+azure-web-pubsub/src/server.ts +28 -0
  30. package/template/+express+azure-web-pubsub/src/store.ts +77 -0
  31. package/template/+express+azure-web-pubsub/src/types.ts +6 -0
  32. package/template/+express+azure-web-pubsub/src/utils.ts +33 -0
  33. package/template/+express+azure-web-pubsub/src/validate.ts +22 -0
  34. package/template/+express+azure-web-pubsub/tsconfig.json +16 -0
  35. package/template/+express+websockets/README.md +38 -0
  36. package/template/+express+websockets/eslint.config.js +11 -0
  37. package/template/+express+websockets/example.env +3 -0
  38. package/template/+express+websockets/example.gitignore +37 -0
  39. package/template/+express+websockets/src/api/v1/controllers/delImage.ts +22 -0
  40. package/template/+express+websockets/src/api/v1/controllers/getHealth.ts +5 -0
  41. package/template/+express+websockets/src/api/v1/controllers/getImage.ts +35 -0
  42. package/template/+express+websockets/src/api/v1/controllers/getImages.ts +19 -0
  43. package/template/+express+websockets/src/api/v1/controllers/getRoomConnect.ts +9 -0
  44. package/template/+express+websockets/src/api/v1/controllers/postRemoveBackground.ts +60 -0
  45. package/template/+express+websockets/src/api/v1/controllers/postUploadImage.ts +35 -0
  46. package/template/+express+websockets/src/api/v1/router.ts +44 -0
  47. package/template/+express+websockets/src/app.ts +32 -0
  48. package/template/+express+websockets/src/config/config.ts +38 -0
  49. package/template/+express+websockets/src/constants.ts +1 -0
  50. package/template/+express+websockets/src/images/persistence.ts +240 -0
  51. package/template/+express+websockets/src/logger/logger.ts +24 -0
  52. package/template/+express+websockets/src/middlewares/body-parser.ts +11 -0
  53. package/template/+express+websockets/src/middlewares/cors.ts +16 -0
  54. package/template/+express+websockets/src/middlewares/http-logger.ts +13 -0
  55. package/template/+express+websockets/src/middlewares/http-response-headers.ts +14 -0
  56. package/template/+express+websockets/src/server.ts +28 -0
  57. package/template/+express+websockets/src/store.ts +69 -0
  58. package/template/+express+websockets/src/types.ts +6 -0
  59. package/template/+express+websockets/src/utils.ts +33 -0
  60. package/template/+express+websockets/src/validate.ts +22 -0
  61. package/template/+express+websockets/tsconfig.json +16 -0
  62. package/template/package.json +55 -0
@@ -0,0 +1,77 @@
1
+ import path from 'path';
2
+ import fs from 'fs/promises';
3
+ import { WeaveAzureWebPubsubServer } from '@inditextech/weave-store-azure-web-pubsub/server';
4
+ import { createFolder, existsFolder } from '@/utils';
5
+
6
+ const endpoint = process.env.WEAVE_AZURE_WEB_PUBSUB_ENDPOINT;
7
+ const key = process.env.WEAVE_AZURE_WEB_PUBSUB_KEY;
8
+ const hubName = process.env.WEAVE_AZURE_WEB_PUBSUB_HUB_NAME;
9
+
10
+ if (!endpoint || !key || !hubName) {
11
+ throw new Error('Missing required environment variables');
12
+ }
13
+
14
+ let azureWebPubsubServer: WeaveAzureWebPubsubServer | null = null;
15
+
16
+ export const getAzureWebPubsubServer = () => {
17
+ if (!azureWebPubsubServer) {
18
+ throw new Error('Azure Web Pubsub server not initialized');
19
+ }
20
+
21
+ return azureWebPubsubServer;
22
+ };
23
+
24
+ export const setupStore = () => {
25
+ azureWebPubsubServer = new WeaveAzureWebPubsubServer({
26
+ pubsubConfig: {
27
+ endpoint,
28
+ key,
29
+ hubName,
30
+ },
31
+ fetchRoom: async (docName: string) => {
32
+ try {
33
+ const roomsFolder = path.join(__dirname, 'rooms');
34
+
35
+ if (!(await existsFolder(roomsFolder))) {
36
+ await createFolder(roomsFolder);
37
+ }
38
+
39
+ const roomsFile = path.join(roomsFolder, docName);
40
+ return await fs.readFile(roomsFile);
41
+ } catch (ex) {
42
+ console.error(ex);
43
+ return null;
44
+ }
45
+ },
46
+ persistRoom: async (
47
+ docName: string,
48
+ actualState: Uint8Array<ArrayBufferLike>
49
+ ) => {
50
+ try {
51
+ const roomsFolder = path.join(__dirname, 'rooms');
52
+
53
+ if (!(await existsFolder(roomsFolder))) {
54
+ await createFolder(roomsFolder);
55
+ }
56
+
57
+ let folderExists = false;
58
+ try {
59
+ await fs.access(roomsFolder);
60
+ folderExists = true;
61
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
62
+ } catch (e) {
63
+ folderExists = false;
64
+ }
65
+
66
+ if (!folderExists) {
67
+ await fs.mkdir(roomsFolder, { recursive: true });
68
+ }
69
+
70
+ const roomsFile = path.join(roomsFolder, docName);
71
+ await fs.writeFile(roomsFile, actualState);
72
+ } catch (ex) {
73
+ console.error(ex);
74
+ }
75
+ },
76
+ });
77
+ };
@@ -0,0 +1,6 @@
1
+ export type ServiceConfig = {
2
+ service: {
3
+ hostname: string;
4
+ port: number;
5
+ };
6
+ };
@@ -0,0 +1,33 @@
1
+ import fs from 'fs/promises';
2
+
3
+ export const getFileContents = async (
4
+ filePath: string,
5
+ encoding: BufferEncoding = 'utf-8',
6
+ ): Promise<string> => {
7
+ try {
8
+ const content = await fs.readFile(filePath, { encoding });
9
+ return content;
10
+ } catch (err) {
11
+ console.error(
12
+ `Error reading file ${filePath}: ${err instanceof Error ? err.message : err}`,
13
+ );
14
+ throw err;
15
+ }
16
+ };
17
+
18
+ export const existsFolder = async (folderPath: string) => {
19
+ try {
20
+ const stats = await fs.stat(folderPath);
21
+ return stats.isDirectory();
22
+ } catch (err) {
23
+ return false;
24
+ }
25
+ };
26
+
27
+ export const createFolder = async (folderPath: string): Promise<void> => {
28
+ try {
29
+ await fs.mkdir(folderPath, { recursive: true });
30
+ } catch (err) {
31
+ throw err;
32
+ }
33
+ };
@@ -0,0 +1,22 @@
1
+ import { ZodError } from "zod";
2
+ import { getServiceConfig } from "./config/config.js";
3
+ import { ServiceConfig } from "./types.js";
4
+ import { getLogger } from "./logger/logger.js";
5
+
6
+ export function validateServiceConfig() {
7
+ let config: ServiceConfig | null = null;
8
+ const logger = getLogger().child({ module: "validate" });
9
+
10
+ try {
11
+ config = getServiceConfig();
12
+ } catch (ex) {
13
+ // console.log(ex);
14
+ if (ex instanceof ZodError) {
15
+ for (const issue of ex.issues) {
16
+ logger.error(issue.message);
17
+ }
18
+ }
19
+ }
20
+
21
+ return config;
22
+ }
@@ -0,0 +1,16 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "es2020",
4
+ "module": "NodeNext",
5
+ "rootDir": "./src",
6
+ "moduleResolution": "nodenext",
7
+ "outDir": "./dist",
8
+ "esModuleInterop": true,
9
+ "forceConsistentCasingInFileNames": true,
10
+ "strict": true,
11
+ "skipLibCheck": true,
12
+ "paths": {
13
+ "@/*": ["./src/*"]
14
+ }
15
+ }
16
+ }
@@ -0,0 +1,38 @@
1
+ # Weave.js Backend start template
2
+
3
+ This is an Express.js application that was generated with **Create Weave.js Backend**
4
+
5
+ This server setup the Weave.js
6
+ [Azure WebSockets](https://inditextech.github.io/weavejs/docs/main/build/stores/websockets-store) store. No extra setup needed.
7
+
8
+ ## Quickstart
9
+
10
+ If you skipped the automatic installation of the dependencies, run first this
11
+ command:
12
+
13
+ ```bash
14
+ npm install
15
+ # or
16
+ pnpm install
17
+ # or
18
+ yarn install
19
+ ```
20
+
21
+ Now, lets run the development server:
22
+
23
+ ```bash
24
+ npm run dev
25
+ # or
26
+ pnpm dev
27
+ # or
28
+ yarn dev
29
+ ```
30
+
31
+ ## Learn more
32
+
33
+ To learn more about Express.js and Weave.js, take a look at the following
34
+ resources:
35
+
36
+ - [Express.js](https://expressjs.com/) - learn about Express.js
37
+ - [Weave.js](https://inditextech.github.io/weavejs) - learn about Weave.js
38
+ - [Weave.js repository](https://github.com/InditexTech/weavejs) - check out out code.
@@ -0,0 +1,11 @@
1
+ import globals from "globals";
2
+ import pluginJs from "@eslint/js";
3
+ import tseslint from "typescript-eslint";
4
+
5
+ /** @type {import('eslint').Linter.Config[]} */
6
+ export default [
7
+ {files: ["**/*.{js,mjs,cjs,ts}"], ignores: ["dist/**/*"],},
8
+ {languageOptions: { globals: globals.node }},
9
+ pluginJs.configs.recommended,
10
+ ...tseslint.configs.recommended,
11
+ ];
@@ -0,0 +1,3 @@
1
+ HOSTNAME=0.0.0.0
2
+ PORT=8080
3
+ LOG_LEVEL=debug
@@ -0,0 +1,37 @@
1
+ # Logs
2
+ logs
3
+ *.log
4
+ npm-debug.log*
5
+ yarn-debug.log*
6
+ yarn-error.log*
7
+ pnpm-debug.log*
8
+ lerna-debug.log*
9
+ reports
10
+
11
+ node_modules
12
+ dist
13
+ dist-ssr
14
+ *.local
15
+ vite.config.ts*
16
+ !vite.config.ts
17
+
18
+ # Editor directories and files
19
+ .vscode/*
20
+ !.vscode/extensions.json
21
+ .idea
22
+ .DS_Store
23
+ *.suo
24
+ *.ntvs*
25
+ *.njsproj
26
+ *.sln
27
+ *.sw?
28
+
29
+ *.pem
30
+ .env*
31
+ .npmrc
32
+
33
+ images
34
+ images-mimetype
35
+ rooms
36
+ temp
37
+ public
@@ -0,0 +1,22 @@
1
+ import { Request, Response } from "express";
2
+ import { ImagesPersistenceHandler } from "../../../images/persistence.js";
3
+
4
+ export const delImageController = () => {
5
+ const persistenceHandler = new ImagesPersistenceHandler()
6
+
7
+ return async (req: Request, res: Response): Promise<void> => {
8
+ const roomId = req.params.roomId;
9
+ const imageId = req.params.imageId;
10
+
11
+ const fileName = `${roomId}/${imageId}`;
12
+
13
+ const result = await persistenceHandler.delete(fileName);
14
+
15
+ if (result) {
16
+ res.status(200).json({ status: "KO", message: "Image deleted" });
17
+ return;
18
+ }
19
+
20
+ res.status(404).json({ status: "KO", message: "Image not found" });
21
+ };
22
+ }
@@ -0,0 +1,5 @@
1
+ import { Request, Response } from "express";
2
+
3
+ export const getHealthController = () => (req: Request, res: Response): void => {
4
+ res.status(200).json({ status: "OK" });
5
+ };
@@ -0,0 +1,35 @@
1
+ import { Request, Response } from 'express';
2
+ import { ImagesPersistenceHandler } from '../../../images/persistence.js';
3
+
4
+ export const getImageController = () => {
5
+ const persistenceHandler = new ImagesPersistenceHandler();
6
+
7
+ return async (req: Request, res: Response): Promise<void> => {
8
+ const roomId = req.params.roomId;
9
+ const imageId = req.params.imageId;
10
+
11
+ const fileName = `${roomId}/${imageId}`;
12
+
13
+ if (!(await persistenceHandler.exists(fileName))) {
14
+ res.status(404).json({ status: 'KO', message: "Image doesn't exists" });
15
+ return;
16
+ }
17
+
18
+ const filePath = await persistenceHandler.getFilePath(fileName);
19
+
20
+ if (filePath) {
21
+ const mimeType = await persistenceHandler.getMimeType(fileName);
22
+ res.setHeader('Content-Type', mimeType);
23
+ res.sendFile(filePath, (err) => {
24
+ if (err) {
25
+ console.error('File not found or error sending file:', err.message);
26
+ res.status(404).send('Image not found');
27
+ }
28
+ });
29
+ } else {
30
+ res
31
+ .status(500)
32
+ .json({ status: 'KO', message: 'Error downloading image' });
33
+ }
34
+ };
35
+ };
@@ -0,0 +1,19 @@
1
+ import { Request, Response } from 'express';
2
+ import { ImagesPersistenceHandler } from '../../../images/persistence.js';
3
+
4
+ export const getImagesController = () => {
5
+ const persistenceHandler = new ImagesPersistenceHandler();
6
+
7
+ return async (req: Request, res: Response): Promise<void> => {
8
+ const roomId = req.params.roomId;
9
+
10
+ const pageSize = parseInt(
11
+ (req.query.pageSize as string | undefined) ?? '20',
12
+ );
13
+ const page = parseInt((req.query.page as string | undefined) ?? '1');
14
+
15
+ const images = await persistenceHandler.list(roomId, pageSize, page);
16
+
17
+ res.status(200).json(images);
18
+ };
19
+ };
@@ -0,0 +1,9 @@
1
+ import { Request, Response } from 'express';
2
+
3
+ export const getRoomConnectController =
4
+ () =>
5
+ async (req: Request, res: Response): Promise<void> => {
6
+ const roomId = req.params.roomId;
7
+
8
+ res.status(200).json({ roomId });
9
+ };
@@ -0,0 +1,60 @@
1
+ import fs from 'node:fs';
2
+ import path from 'node:path';
3
+ import { Request, Response } from 'express';
4
+ import { removeBackground } from '@imgly/background-removal-node';
5
+ import { ImagesPersistenceHandler } from '../../../images/persistence.js';
6
+
7
+ async function myBlobToUIntDemo(blob: Blob) {
8
+ const arrayBuffer = await blob.arrayBuffer();
9
+ const buffer = Buffer.from(arrayBuffer);
10
+ return new Uint8Array(buffer);
11
+ }
12
+
13
+ export const postRemoveBackgroundController = () => {
14
+ const persistenceHandler = new ImagesPersistenceHandler();
15
+
16
+ return async (req: Request, res: Response): Promise<void> => {
17
+ const roomId = req.params.roomId;
18
+ const imageId = req.params.imageId;
19
+
20
+ const fileName = `${roomId}/${imageId}`;
21
+
22
+ if (!(await persistenceHandler.exists(fileName))) {
23
+ res.status(404).json({ status: 'KO', message: "Image doesn't exists" });
24
+ return;
25
+ }
26
+
27
+ try {
28
+ const filePathDownload = await persistenceHandler.getFilePath(fileName);
29
+
30
+ removeBackground(filePathDownload, {
31
+ publicPath: `file://${path.join(process.cwd(), 'public')}/`,
32
+ output: { format: 'image/png', quality: 1 },
33
+ })
34
+ .then(async (blob: Blob) => {
35
+ // The result is a blob encoded as PNG. It can be converted to an URL to be used as HTMLImage.src
36
+ const data = await myBlobToUIntDemo(blob);
37
+ const fileNameRemoved = `${fileName}-removed`;
38
+ await persistenceHandler.persist(fileNameRemoved, 'image/png', data);
39
+ fs.rmSync(filePathDownload);
40
+
41
+ res.status(201).json({
42
+ status: 'Image created OK',
43
+ fileName: fileNameRemoved,
44
+ mimeType: 'image/png',
45
+ });
46
+ })
47
+ .catch((err) => {
48
+ console.error(err);
49
+ res
50
+ .status(500)
51
+ .json({ status: 'KO', message: 'Error transforming the image' });
52
+ });
53
+ } catch (ex) {
54
+ console.error(ex);
55
+ res
56
+ .status(500)
57
+ .json({ status: 'KO', message: 'Error downloading the image' });
58
+ }
59
+ };
60
+ };
@@ -0,0 +1,35 @@
1
+ import { Request, Response } from 'express';
2
+ import { v4 as uuidv4 } from 'uuid';
3
+ import { ImagesPersistenceHandler } from '../../../images/persistence.js';
4
+
5
+ export const postUploadImageController = () => {
6
+ const persistenceHandler = new ImagesPersistenceHandler();
7
+
8
+ return async (req: Request, res: Response): Promise<void> => {
9
+ const file = req.file;
10
+
11
+ const roomId = req.params.roomId;
12
+ const mimeType = file?.mimetype ?? 'application/octet-stream';
13
+ const data = file?.buffer ?? new Uint8Array();
14
+
15
+ const fileName = `${roomId}/${uuidv4()}`;
16
+
17
+ if (await persistenceHandler.exists(fileName)) {
18
+ res.status(500).json({ status: 'KO', message: 'Image already exists' });
19
+ }
20
+
21
+ try {
22
+ if (file) {
23
+ await persistenceHandler.persist(fileName, mimeType, data);
24
+ res
25
+ .status(201)
26
+ .json({ status: 'Image created OK', fileName, mimeType });
27
+ } else {
28
+ res.status(500).json({ status: 'KO', message: 'Error creating image' });
29
+ }
30
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
31
+ } catch (error) {
32
+ res.status(500).json({ status: 'KO', message: 'Error creating image' });
33
+ }
34
+ };
35
+ };
@@ -0,0 +1,44 @@
1
+ import { Express, Router } from 'express';
2
+ import multer from 'multer';
3
+ import { getHealthController } from './controllers/getHealth.js';
4
+ import { getRoomConnectController } from './controllers/getRoomConnect.js';
5
+ import { getImageController } from './controllers/getImage.js';
6
+ import { postUploadImageController } from './controllers/postUploadImage.js';
7
+ import { delImageController } from './controllers/delImage.js';
8
+ import { getImagesController } from './controllers/getImages.js';
9
+ import { postRemoveBackgroundController } from './controllers/postRemoveBackground.js';
10
+
11
+ const router: Router = Router();
12
+
13
+ export function getApiV1Router() {
14
+ return router;
15
+ }
16
+
17
+ export function setupApiV1Router(app: Express) {
18
+ const router: Router = Router();
19
+
20
+ // Setup multer to upload files
21
+ const upload = multer();
22
+
23
+ // Setup router routes
24
+ router.get(`/health`, getHealthController());
25
+
26
+ // Room handling API
27
+ router.get(`/sync/rooms/:roomId`, getRoomConnectController());
28
+
29
+ // Images handling API
30
+ router.get(`/rooms/:roomId/images`, getImagesController());
31
+ router.get(`/rooms/:roomId/images/:imageId`, getImageController());
32
+ router.post(
33
+ `/rooms/:roomId/images/:imageId/remove-background`,
34
+ postRemoveBackgroundController(),
35
+ );
36
+ router.post(
37
+ `/rooms/:roomId/images`,
38
+ upload.single('file'),
39
+ postUploadImageController(),
40
+ );
41
+ router.delete(`/rooms/:roomId/images/:imageId`, delImageController());
42
+
43
+ app.use('/api/v1', router);
44
+ }
@@ -0,0 +1,32 @@
1
+ import express, { type Express } from "express";
2
+ import { setupHttpLoggerMiddleware } from "./middlewares/http-logger.js";
3
+ import { setupCorsMiddleware } from "./middlewares/cors.js";
4
+ import { setupHttpResponseHeadersMiddleware } from "./middlewares/http-response-headers.js";
5
+ import { setupBodyParserMiddleware } from "./middlewares/body-parser.js";
6
+ import { setupApiV1Router } from "./api/v1/router.js";
7
+
8
+ let app: Express | null = null;
9
+
10
+ export function getApp() {
11
+ if (!app) {
12
+ throw new Error("App not initialized");
13
+ }
14
+
15
+ return app;
16
+ }
17
+
18
+ export function setupApp() {
19
+ // Setup the service
20
+ app = express();
21
+
22
+ // Setup Middlewares
23
+ setupHttpLoggerMiddleware(app);
24
+ setupCorsMiddleware(app);
25
+ setupHttpResponseHeadersMiddleware(app);
26
+ setupBodyParserMiddleware(app);
27
+
28
+ // Setup Routers
29
+ setupApiV1Router(app);
30
+
31
+ return app;
32
+ }
@@ -0,0 +1,38 @@
1
+ import { z } from 'zod';
2
+ import { ServiceConfig } from '../types.js';
3
+ import { DEFAULT_PORT } from '../constants.js';
4
+
5
+ const serviceConfigSchema = z.object({
6
+ service: z.object({
7
+ hostname: z
8
+ .string({
9
+ required_error:
10
+ 'Define the service hostname on the environment variable HOSTNAME',
11
+ })
12
+ .trim()
13
+ .optional()
14
+ .default('0.0.0.0'),
15
+ port: z
16
+ .number({
17
+ required_error:
18
+ 'Define the service port on the environment variable PORT',
19
+ })
20
+ .int({ message: 'The post must be an integer' })
21
+ .optional()
22
+ .default(DEFAULT_PORT),
23
+ }),
24
+ });
25
+
26
+ export function getServiceConfig(): ServiceConfig {
27
+ const hostname = process.env.HOSTNAME;
28
+ const port = parseInt(process.env.PORT || `${DEFAULT_PORT}`);
29
+
30
+ const service = {
31
+ hostname,
32
+ port,
33
+ };
34
+
35
+ const serviceConfig = { service };
36
+
37
+ return serviceConfigSchema.parse(serviceConfig);
38
+ }
@@ -0,0 +1 @@
1
+ export const DEFAULT_PORT = 3000;