node-server-engine 1.8.0 → 1.9.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 (35) hide show
  1. package/README.md +712 -67
  2. package/dist/entities/AWSS3/AWSS3.d.ts +157 -0
  3. package/dist/entities/AWSS3/AWSS3.js +426 -0
  4. package/dist/entities/AWSS3/AWSS3.js.map +1 -0
  5. package/dist/entities/AWSS3/AWSS3.types.d.ts +78 -0
  6. package/dist/entities/AWSS3/AWSS3.types.js +2 -0
  7. package/dist/entities/AWSS3/AWSS3.types.js.map +1 -0
  8. package/dist/entities/AWSS3/index.d.ts +2 -0
  9. package/dist/entities/{Storage → AWSS3}/index.js +8 -8
  10. package/dist/entities/AWSS3/index.js.map +1 -0
  11. package/dist/entities/ElasticSearch/ElasticSearch.d.ts +5 -0
  12. package/dist/entities/ElasticSearch/ElasticSearch.js +137 -38
  13. package/dist/entities/ElasticSearch/ElasticSearch.js.map +1 -1
  14. package/dist/entities/GoogleCloudStorage/GoogleCloudStorage.d.ts +141 -0
  15. package/dist/entities/GoogleCloudStorage/GoogleCloudStorage.js +351 -0
  16. package/dist/entities/GoogleCloudStorage/GoogleCloudStorage.js.map +1 -0
  17. package/dist/entities/{Storage/Storage.types.d.ts → GoogleCloudStorage/GoogleCloudStorage.types.d.ts} +14 -0
  18. package/dist/entities/GoogleCloudStorage/GoogleCloudStorage.types.js +2 -0
  19. package/dist/entities/GoogleCloudStorage/GoogleCloudStorage.types.js.map +1 -0
  20. package/dist/entities/GoogleCloudStorage/index.d.ts +2 -0
  21. package/dist/entities/GoogleCloudStorage/index.js +28 -0
  22. package/dist/entities/GoogleCloudStorage/index.js.map +1 -0
  23. package/dist/entities/Localizator/Localizator.js +2 -2
  24. package/dist/entities/Localizator/Localizator.js.map +1 -1
  25. package/dist/entities/index.d.ts +2 -1
  26. package/dist/entities/index.js +15 -4
  27. package/dist/entities/index.js.map +1 -1
  28. package/package.json +11 -10
  29. package/dist/entities/Storage/Storage.d.ts +0 -38
  30. package/dist/entities/Storage/Storage.js +0 -214
  31. package/dist/entities/Storage/Storage.js.map +0 -1
  32. package/dist/entities/Storage/Storage.types.js +0 -2
  33. package/dist/entities/Storage/Storage.types.js.map +0 -1
  34. package/dist/entities/Storage/index.d.ts +0 -2
  35. package/dist/entities/Storage/index.js.map +0 -1
package/README.md CHANGED
@@ -8,18 +8,21 @@ Framework used to develop Node backend services. This package ships with a lot o
8
8
 
9
9
  ## Features
10
10
 
11
- - 🚀 **Express-based** - Built on the popular Express.js framework
11
+ - 🚀 **Express-based** - Built on the popular Express.js framework (v4.22)
12
12
  - 🔒 **Multiple Auth Methods** - JWT, mTLS, HMAC, and Static token authentication
13
- - 🔌 **WebSocket Support** - Built-in WebSocket server with message handling
14
- - 📊 **Database Integration** - Sequelize ORM with migrations support
15
- - 📡 **Pub/Sub** - Google Cloud Pub/Sub integration
13
+ - 🔌 **WebSocket Support** - Built-in WebSocket server with message handling (ws v8.18)
14
+ - 📊 **Database Integration** - Sequelize ORM with migrations support (v6.37)
15
+ - 📡 **Pub/Sub** - Google Cloud Pub/Sub integration (v4.11)
16
16
  - 🔔 **Push Notifications** - Built-in push notification support
17
17
  - 🌐 **i18n** - Internationalization with translation management
18
- - 🔍 **ElasticSearch** - Full-text search integration
18
+ - 🔍 **ElasticSearch** - Full-text search integration (v9.2) with auto-migrations
19
+ - 💾 **Redis** - Advanced Redis client with retry logic, TLS support (ioredis v5.8)
19
20
  - 📝 **API Documentation** - Swagger/OpenAPI documentation support
20
- - 📤 **File Uploads** - Single and chunked file upload middleware
21
+ - 📤 **File Uploads** - Single and chunked file upload middleware with validation
21
22
  - 🧪 **TypeScript** - Written in TypeScript with full type definitions
22
23
  - ✅ **Modern Tooling** - ESLint, Prettier, and automated versioning
24
+ - 🛡️ **Permission System** - Role-based access control with case-insensitive matching
25
+ - 🔐 **Security** - HMAC authentication, TLS/mTLS support, input validation
23
26
 
24
27
  ## Requirements
25
28
 
@@ -477,29 +480,86 @@ new Endpoint({
477
480
 
478
481
  ### Socket Client
479
482
 
480
- The socket server start if the [Server](#server)'s webSocket option is not undefined.
483
+ The WebSocket server starts automatically if the [Server](#server)'s `webSocket` option is provided.
481
484
 
482
- Each time a new socket connection is established, a new instance of `SocketClient` is created.
485
+ Each WebSocket connection creates a new `SocketClient` instance with built-in authentication, message routing, and connection management.
486
+
487
+ #### Features
488
+
489
+ - **JWT Authentication**: Secure token-based authentication with automatic renewal
490
+ - **Message Routing**: Type-based message handlers similar to HTTP endpoints
491
+ - **Connection Health**: Built-in ping/pong mechanism with configurable intervals
492
+ - **Lifecycle Callbacks**: Hooks for initialization, authentication, and shutdown
493
+ - **Error Handling**: Automatic error formatting and client notification
483
494
 
484
495
  #### Socket Client Options
485
496
 
486
- Options can be set for SocketClient when setting up a Server instance.
497
+ Options can be set when configuring the Server's webSocket:
498
+
499
+ ```javascript
500
+ import { Server, MessageHandler } from 'node-server-engine';
501
+
502
+ const server = new Server({
503
+ webSocket: {
504
+ client: {
505
+ handlers: [messageHandler1, messageHandler2],
506
+ initCallbacks: (client) => {
507
+ console.log(`Client ${client.id} connected`);
508
+ },
509
+ authCallbacks: (client) => {
510
+ const user = client.getUser();
511
+ console.log(`User ${user.userId} authenticated`);
512
+ },
513
+ shutdownCallbacks: (client) => {
514
+ console.log(`Client ${client.id} disconnected`);
515
+ }
516
+ }
517
+ }
518
+ });
519
+ ```
487
520
 
488
521
  | Parameter | Type | Description | Default |
489
522
  | ----------------- | ------------------------------------------ | ----------------------------------------------------------------------- | ------------ |
490
523
  | handlers | Array\<[MessageHandler](#message-handler)> | A list of message handlers to use | **required** |
491
- | authCallbacks | Array\<function> | Callbacks called when a client successfully authenticates on the socket | [] |
492
- | initCallbacks | Array\<function> | Callbacks called when the socket client is created | [] |
493
- | shutdownCallbacks | Array\<function> | Callbacks called when the socket client is destroyed | [] |
524
+ | authCallbacks | Function \| Array\<Function> | Callbacks called when a client successfully authenticates | [] |
525
+ | initCallbacks | Function \| Array\<Function> | Callbacks called when the socket client is created | [] |
526
+ | shutdownCallbacks | Function \| Array\<Function> | Callbacks called when the socket client is destroyed | [] |
527
+
528
+ #### Client Properties & Methods
529
+
530
+ | Property/Method | Type | Description |
531
+ | ----------------- | --------------------------- | ------------------------------------------------------------------------- |
532
+ | id | String | Unique identifier for the connection |
533
+ | establishedAt | Date | Timestamp when connection was established |
534
+ | isAuthenticated() | () => Boolean | Check if client is authenticated |
535
+ | getUser() | () => SocketUser \| undefined | Get authenticated user data (userId, deviceId, tokenId, audience) |
536
+ | sendMessage() | (type, payload, options) | Send a message to the client |
537
+
538
+ #### Client Authentication
539
+
540
+ Clients authenticate by sending a message:
541
+
542
+ ```javascript
543
+ // Client-side
544
+ websocket.send(JSON.stringify({
545
+ type: 'authenticate',
546
+ payload: { token: 'your-jwt-token' }
547
+ }));
548
+
549
+ // Server will:
550
+ // 1. Verify the JWT token
551
+ // 2. Extract user information (userId, deviceId, audience)
552
+ // 3. Set authentication status
553
+ // 4. Trigger authCallbacks
554
+ // 5. Send renewal reminder 1 minute before token expiration
555
+ ```
494
556
 
495
- #### Properties
557
+ #### Environment Variables
496
558
 
497
- | Property | Type | Description |
498
- | ------------- | ------- | ---------------------------------------------------------------------------------------------------------------------- |
499
- | id | String | A unique identifier for the connection |
500
- | ws | Object | The socket being used. Instance of [WebSocket](https://github.com/websockets/ws/blob/master/doc/ws.md#class-websocket) |
501
- | authenticated | Boolean | Flag indicating that the current user is authenticated |
502
- | userId | String | Id of the current user **(available when authenticated)** |
559
+ | Variable | Description | Default |
560
+ | ------------------- | ----------------------------------------------- | ------- |
561
+ | WS_PING_INTERVAL | Interval between ping checks (seconds) | 30 |
562
+ | WEBSOCKET_CLOSE_TIMEOUT | Timeout for graceful close (milliseconds) | 3000 |
503
563
 
504
564
  ---
505
565
 
@@ -532,24 +592,235 @@ new Server({
532
592
 
533
593
  ### Redis
534
594
 
535
- The server engine exposes a Redis client that is configured to work with the standard environment variables that are used in our services.
595
+ The server engine exposes a Redis client configured for production use with automatic reconnection, retry logic, and optional TLS support.
536
596
 
537
- It is a pre-configured instance of [ioredis](https://github.com/luin/ioredis). See the package documentation for more details.
597
+ It is a pre-configured instance of [ioredis](https://github.com/luin/ioredis) v5.8.2. See the package documentation for more details.
538
598
 
539
599
  ```javascript
540
600
  import { Redis } from 'node-server-engine';
541
601
 
542
- await Redis.set(key, value);
602
+ // Initialize (automatically called by Server, or manually)
603
+ Redis.init();
604
+
605
+ // Use Redis commands
606
+ await Redis.set('key', 'value');
607
+ const value = await Redis.get('key');
608
+ await Redis.del('key');
609
+ await Redis.expire('key', 3600);
610
+
611
+ // Get underlying client for advanced use
612
+ const client = Redis.getClient();
613
+
614
+ // Shutdown when done
615
+ await Redis.shutdown();
543
616
  ```
544
617
 
545
- It can be configured through environment variables
618
+ #### Features
619
+
620
+ - **Automatic Reconnection**: Exponential backoff retry strategy (up to 2s delay)
621
+ - **Error Recovery**: Reconnects on READONLY, ECONNRESET, and ETIMEDOUT errors
622
+ - **Connection Management**: Event listeners for connect, ready, error, close, reconnecting
623
+ - **TLS Support**: Automatic TLS configuration when `TLS_CA` is provided
624
+ - **Test Mode**: Lazy connection in test environments to prevent connection attempts
625
+
626
+ #### Environment Variables
627
+
628
+ | Variable | Description | Default | Required |
629
+ | --------------- | -------------------------------------- | ------- | -------- |
630
+ | REDIS_HOST | Redis server hostname or IP | - | ✓ |
631
+ | REDIS_PORT | Redis server port | 6379 | ✗ |
632
+ | REDIS_USERNAME | Username for authentication | - | ✗ |
633
+ | REDIS_PASSWORD | Password for authentication | - | ✗ |
634
+ | TLS_CA | TLS certificate authority for SSL/TLS | - | ✗ |
635
+
636
+ #### Configuration Options
546
637
 
547
- | env | description | default |
548
- | -------------- | ---------------------------------------------------------------------------------- | ------- |
549
- | REDIS_HOST | Host to which to connecto to | |
550
- | REDIS_PORT | Port on which redis is served | |
551
- | REDIS_PASSWORD | Password used to authenticate with the server | |
552
- | REDIS_CLUSTER | Flag indicating that redis is configured as a cluster and not as a single instance | false |
638
+ You can customize Redis client creation:
639
+
640
+ ```javascript
641
+ import { createRedisClient } from 'node-server-engine';
642
+
643
+ const client = createRedisClient({
644
+ db: 2, // Database index (default: 0)
645
+ lazyConnect: true, // Don't connect until first command
646
+ enableReadyCheck: false, // Disable ready check
647
+ redis: { // Override any ioredis options
648
+ connectTimeout: 10000,
649
+ commandTimeout: 5000
650
+ }
651
+ });
652
+ ```
653
+
654
+ ---
655
+
656
+ ### AWSS3
657
+
658
+ The AWSS3 entity is a generic wrapper around AWS S3 (and S3-compatible services like MinIO, LocalStack). It supports uploads, downloads, streaming, metadata, and deletion with size validation and UUID-based path generation.
659
+
660
+ Based on @aws-sdk/client-s3 v3.
661
+
662
+ ```javascript
663
+ import { AWSS3 } from 'node-server-engine';
664
+ import fs from 'fs';
665
+
666
+ // Initialize with configuration (optional; otherwise uses environment variables)
667
+ AWSS3.init({
668
+ region: 'us-east-1',
669
+ accessKeyId: 'YOUR_ACCESS_KEY',
670
+ secretAccessKey: 'YOUR_SECRET_KEY',
671
+ // For S3-compatible services
672
+ // endpoint: 'http://localhost:4566',
673
+ // forcePathStyle: true
674
+ });
675
+
676
+ // Or rely on environment variables and auto-init
677
+ // AWSS3.init();
678
+
679
+ // Upload a file
680
+ const fileStream = fs.createReadStream('photo.jpg');
681
+ const uploaded = await AWSS3.upload(
682
+ fileStream,
683
+ 'my-bucket',
684
+ { directory: 'photos', mime: 'image/jpeg' },
685
+ { maxSize: '5MB' },
686
+ { userId: '123' } // optional metadata
687
+ );
688
+ console.log(uploaded.Key); // photos/<uuid>.jpeg
689
+
690
+ // Download entire file into memory
691
+ const { data, metadata } = await AWSS3.get('my-bucket', uploaded.Key);
692
+ console.log(metadata.ContentLength);
693
+
694
+ // Stream a large file
695
+ const { stream } = await AWSS3.download('my-bucket', uploaded.Key);
696
+ stream.pipe(res);
697
+
698
+ // Get a fast stream without metadata
699
+ const s = await AWSS3.getFileStream('my-bucket', uploaded.Key);
700
+ s.pipe(res);
701
+
702
+ // Metadata only
703
+ const head = await AWSS3.getMetadata('my-bucket', uploaded.Key);
704
+
705
+ // Delete
706
+ await AWSS3.delete('my-bucket', uploaded.Key);
707
+
708
+ // Generate unique destination path
709
+ const key = AWSS3.generateFileDestination({ directory: 'uploads/images', mime: 'image/png' });
710
+ ```
711
+
712
+ #### Features
713
+
714
+ - Auto-initialization via environment variables
715
+ - Stream-based upload with `maxSize` validation
716
+ - Full download, streaming download, or stream-only access
717
+ - UUID-based file naming with directory and extension inference from MIME
718
+ - S3-compatible (supports custom `endpoint` and `forcePathStyle`)
719
+
720
+ #### API Methods
721
+
722
+ ##### `init(config?)`
723
+
724
+ Initialize the S3 client explicitly; otherwise it will lazy-init using environment variables.
725
+
726
+ ```javascript
727
+ AWSS3.init({
728
+ region: 'us-east-1',
729
+ accessKeyId: '...',
730
+ secretAccessKey: '...',
731
+ sessionToken: '...', // optional
732
+ endpoint: 'http://localhost:9000', // optional (MinIO/LocalStack)
733
+ forcePathStyle: true // optional (S3-compatible)
734
+ });
735
+ ```
736
+
737
+ ##### `upload(stream, bucket, destinationOptions?, uploaderOptions?, metadata?)`
738
+
739
+ Uploads content from a readable stream.
740
+
741
+ - `destinationOptions`: `{ directory?, fileName?, mime?, noExtension? }`
742
+ - `uploaderOptions`: `{ maxSize?: string }` e.g. `5MB`, `100KB`
743
+ - `metadata`: key/value pairs stored as object metadata
744
+
745
+ Returns `Promise<S3UploadedFile>` with keys like `Bucket`, `Key`, `ETag`, `Location`.
746
+
747
+ ##### `get(bucket, key)`
748
+
749
+ Downloads the full file into memory as `Buffer` and returns `{ data, metadata }`.
750
+
751
+ ##### `download(bucket, key)`
752
+
753
+ Returns `{ stream, metadata }` for streaming large files efficiently.
754
+
755
+ ##### `getFileStream(bucket, key)`
756
+
757
+ Returns a `Readable` stream without fetching metadata.
758
+
759
+ ##### `getMetadata(bucket, key)`
760
+
761
+ Returns file metadata via a HEAD request.
762
+
763
+ ##### `delete(bucket, key)`
764
+
765
+ Deletes the object.
766
+
767
+ ##### `generateFileDestination(options?)`
768
+
769
+ Generates a unique key using UUID with optional directory and extension (from `mime`).
770
+
771
+ #### Environment Variables
772
+
773
+ | Variable | Description | Required |
774
+ | --- | --- | --- |
775
+ | `AWS_REGION` | AWS region (e.g., `us-east-1`) | ✗* |
776
+ | `AWS_ACCESS_KEY_ID` | Access key ID | ✗* |
777
+ | `AWS_SECRET_ACCESS_KEY` | Secret access key | ✗* |
778
+ | `AWS_SESSION_TOKEN` | Session token (temporary creds) | ✗ |
779
+ | `AWS_S3_ENDPOINT` | Custom S3-compatible endpoint (MinIO/LocalStack) | ✗ |
780
+ | `AWS_S3_FORCE_PATH_STYLE` | Use path-style addressing (`true`/`false`) | ✗ |
781
+
782
+ \* Not required if you call `init()` with a config object.
783
+
784
+ #### Error Handling
785
+
786
+ - Throws `WebError` with status `413` when `maxSize` is exceeded during upload
787
+ - Other errors bubble up from AWS SDK v3 commands
788
+
789
+ **Example:**
790
+ ```javascript
791
+ try {
792
+ await AWSS3.upload(stream, 'bucket', {}, { maxSize: '1MB' });
793
+ } catch (e) {
794
+ if (e.statusCode === 413) {
795
+ console.log('File too large');
796
+ }
797
+ }
798
+ ```
799
+
800
+ #### Usage in Template Projects
801
+
802
+ ```javascript
803
+ import { AWSS3, Endpoint, Method } from 'node-server-engine';
804
+ import { Readable } from 'stream';
805
+
806
+ new Endpoint({
807
+ path: '/upload-s3',
808
+ method: Method.POST,
809
+ files: [{ key: 'file', maxSize: '10MB', required: true }],
810
+ async handler(req, res) {
811
+ const file = req.files[0];
812
+ const stream = Readable.from(file.buffer);
813
+
814
+ const result = await AWSS3.upload(
815
+ stream,
816
+ process.env.S3_UPLOAD_BUCKET,
817
+ { directory: 'user-uploads', mime: file.mimetype }
818
+ );
819
+
820
+ res.json({ key: result.Key, url: result.Location });
821
+ }
822
+ });
823
+ ```
553
824
 
554
825
  ---
555
826
 
@@ -679,25 +950,362 @@ await Localizator.shutdown();
679
950
 
680
951
  ### ElasticSearch
681
952
 
682
- The ElasticSearch exposes a service related to data searching.
953
+ The ElasticSearch entity provides a managed client for Elasticsearch with automatic migrations, connection management, and TLS support.
954
+
955
+ Based on [@elastic/elasticsearch](https://www.elastic.co/guide/en/elasticsearch/client/javascript-api/current/index.html) v9.2.0.
683
956
 
684
957
  ```javascript
685
958
  import { ElasticSearch } from 'node-server-engine';
686
959
 
687
- // The synchronize init should be called first to initialize the data
960
+ // Initialize (runs migrations automatically)
688
961
  await ElasticSearch.init();
689
962
 
690
- // This should be call when the program shuts down.
963
+ // Get the client for operations
964
+ const client = ElasticSearch.getClient();
965
+
966
+ // Use Elasticsearch operations
967
+ await client.index({
968
+ index: 'products',
969
+ id: '123',
970
+ document: { name: 'Product', price: 99.99 }
971
+ });
972
+
973
+ const result = await client.search({
974
+ index: 'products',
975
+ query: { match: { name: 'Product' } }
976
+ });
977
+
978
+ // Shutdown when done
691
979
  await ElasticSearch.shutdown();
692
980
  ```
693
981
 
694
- | env | description | default |
695
- | ----------------------------- | ---------------------------------------------- | ------------ |
696
- | ELASTIC_SEARCH_MIGRATION_PATH | The path link to Elastic Search migration data | **required** |
697
- | ELASTIC_SEARCH_HOST | The elastic search host url | **required** |
698
- | ELASTIC_SEARCH_USERNAME | The elastic search user name | **required** |
699
- | ELASTIC_SEARCH_PASSWORD | The elastic search password | **required** |
700
- | TLS_CA | CA for ssl config when available | |
982
+ #### Features
983
+
984
+ - **Automatic Migrations**: Tracks and runs migrations on startup
985
+ - **Connection Verification**: Pings cluster on initialization
986
+ - **TLS Support**: Optional SSL/TLS configuration
987
+ - **Retry Logic**: Built-in retry mechanism with configurable attempts
988
+ - **Test Mode**: Auto-flushes indices in test environment
989
+ - **Error Handling**: Detailed error reporting with context
990
+
991
+ #### Migration System
992
+
993
+ Create migration files in your specified migration directory:
994
+
995
+ ```javascript
996
+ // migrations/001-create-products-index.ts
997
+ import { Client } from '@elastic/elasticsearch';
998
+
999
+ export async function migrate(client: Client): Promise<void> {
1000
+ await client.indices.create({
1001
+ index: 'products',
1002
+ settings: {
1003
+ number_of_shards: 1,
1004
+ number_of_replicas: 1
1005
+ },
1006
+ mappings: {
1007
+ properties: {
1008
+ name: { type: 'text' },
1009
+ price: { type: 'double' },
1010
+ createdAt: { type: 'date' }
1011
+ }
1012
+ }
1013
+ });
1014
+ }
1015
+ ```
1016
+
1017
+ Migrations are:
1018
+ - Run automatically on `init()`
1019
+ - Tracked in the `migrations` index
1020
+ - Executed once per file
1021
+ - Run in alphabetical order
1022
+
1023
+ #### Environment Variables
1024
+
1025
+ | Variable | Description | Required |
1026
+ | ------------------------------ | ------------------------------------------ | -------- |
1027
+ | ELASTIC_SEARCH_HOST | Elasticsearch cluster URL | ✓ |
1028
+ | ELASTIC_SEARCH_USERNAME | Username for authentication | ✓ |
1029
+ | ELASTIC_SEARCH_PASSWORD | Password for authentication | ✓ |
1030
+ | ELASTIC_SEARCH_MIGRATION_PATH | Absolute path to migrations directory | ✓ |
1031
+ | TLS_CA | TLS certificate authority for SSL/TLS | ✗ |
1032
+
1033
+ #### Configuration
1034
+
1035
+ The client is configured with:
1036
+ - **Max Retries**: 3 attempts
1037
+ - **Request Timeout**: 30 seconds
1038
+ - **Sniff on Start**: Disabled (use with clusters)
1039
+ - **TLS**: Enabled when `TLS_CA` is provided
1040
+
1041
+ ---
1042
+
1043
+ ### GoogleCloudStorage
1044
+
1045
+ The GoogleCloudStorage entity provides a simple, generic wrapper for Google Cloud Storage operations. It handles file uploads, downloads, streaming, and deletion with built-in size validation and automatic file path generation.
1046
+
1047
+ Based on [@google-cloud/storage](https://cloud.google.com/nodejs/docs/reference/storage/latest) v7.14.0.
1048
+
1049
+ ```javascript
1050
+ import { GoogleCloudStorage } from 'node-server-engine';
1051
+ import fs from 'fs';
1052
+
1053
+ // Initialize with configuration
1054
+ GoogleCloudStorage.init({
1055
+ projectId: 'my-project',
1056
+ keyFilename: '/path/to/keyfile.json'
1057
+ });
1058
+
1059
+ // Or use environment variables (GC_PROJECT, GOOGLE_APPLICATION_CREDENTIALS)
1060
+ GoogleCloudStorage.init();
1061
+
1062
+ // Upload a file
1063
+ const fileStream = fs.createReadStream('photo.jpg');
1064
+ const result = await GoogleCloudStorage.upload(
1065
+ fileStream,
1066
+ 'my-bucket',
1067
+ { directory: 'photos', mime: 'image/jpeg' },
1068
+ { metadata: { contentType: 'image/jpeg' } },
1069
+ { maxSize: '5MB' }
1070
+ );
1071
+ console.log(result.name); // photos/uuid.jpeg
1072
+
1073
+ // Download file as Buffer
1074
+ const { data, metadata } = await GoogleCloudStorage.get('my-bucket', 'photos/image.jpg');
1075
+ console.log(metadata.size); // File size in bytes
1076
+ fs.writeFileSync('downloaded.jpg', data);
1077
+
1078
+ // Stream a file (for large files)
1079
+ const { stream, metadata } = await GoogleCloudStorage.download('my-bucket', 'videos/video.mp4');
1080
+ stream.pipe(res); // Stream to HTTP response
1081
+
1082
+ // Get file stream directly (fastest, no metadata)
1083
+ const stream = GoogleCloudStorage.getFileStream('my-bucket', 'audio/song.mp3');
1084
+ stream.pipe(res);
1085
+
1086
+ // Delete a file
1087
+ await GoogleCloudStorage.delete('my-bucket', 'temp/old-file.txt');
1088
+
1089
+ // Generate unique file paths
1090
+ const path = GoogleCloudStorage.generateFileDestination({
1091
+ directory: 'uploads/images',
1092
+ mime: 'image/jpeg'
1093
+ });
1094
+ // Result: 'uploads/images/a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpeg'
1095
+ ```
1096
+
1097
+ #### Features
1098
+
1099
+ - **Auto-initialization**: Automatically initializes on first use if not manually initialized
1100
+ - **Flexible Configuration**: Supports config object, environment variables, or credentials
1101
+ - **File Upload**: Stream-based uploads with size validation
1102
+ - **Multiple Download Methods**: Full download, streaming, or direct stream access
1103
+ - **Path Generation**: Automatic UUID-based file naming with directory and extension support
1104
+ - **Size Validation**: Built-in file size limits with human-readable formats (5MB, 100KB, etc.)
1105
+ - **Error Handling**: Detailed error reporting with context
1106
+ - **No Project Dependencies**: Generic implementation works with any Google Cloud Storage bucket
1107
+
1108
+ #### API Methods
1109
+
1110
+ ##### `init(config?)`
1111
+
1112
+ Initialize Google Cloud Storage with configuration. Optional - will auto-initialize with environment variables if not called.
1113
+
1114
+ ```javascript
1115
+ GoogleCloudStorage.init({
1116
+ projectId: 'my-project-id',
1117
+ keyFilename: '/path/to/service-account-key.json',
1118
+ // Or use credentials directly
1119
+ credentials: {
1120
+ client_email: 'service@project.iam.gserviceaccount.com',
1121
+ private_key: '-----BEGIN PRIVATE KEY-----\n...'
1122
+ },
1123
+ // For local emulator
1124
+ apiEndpoint: 'http://localhost:9000'
1125
+ });
1126
+ ```
1127
+
1128
+ ##### `upload(stream, bucket, destinationOptions?, storageOptions?, uploaderOptions?)`
1129
+
1130
+ Upload a file to Google Cloud Storage.
1131
+
1132
+ **Parameters:**
1133
+ - `stream` (Readable): Node.js readable stream of file content
1134
+ - `bucket` (string): Bucket name
1135
+ - `destinationOptions` (object, optional):
1136
+ - `directory` (string): Subdirectory path (e.g., 'uploads/images')
1137
+ - `fileName` (string): Specific filename (if not provided, generates UUID)
1138
+ - `mime` (string): MIME type for extension detection
1139
+ - `noExtension` (boolean): Don't append file extension
1140
+ - `storageOptions` (object, optional): Google Cloud Storage write stream options
1141
+ - `uploaderOptions` (object, optional):
1142
+ - `maxSize` (string): Maximum file size (e.g., '5MB', '100KB', '1GB')
1143
+
1144
+ **Returns:** Promise\<StorageUploadedFile> - Metadata of uploaded file
1145
+
1146
+ **Example:**
1147
+ ```javascript
1148
+ const fileStream = fs.createReadStream('document.pdf');
1149
+ const result = await GoogleCloudStorage.upload(
1150
+ fileStream,
1151
+ 'documents-bucket',
1152
+ { directory: 'legal/contracts', mime: 'application/pdf' },
1153
+ { metadata: { contentType: 'application/pdf' } },
1154
+ { maxSize: '10MB' }
1155
+ );
1156
+ ```
1157
+
1158
+ ##### `get(bucket, path)`
1159
+
1160
+ Download a file and return its content as a Buffer along with metadata.
1161
+
1162
+ **Parameters:**
1163
+ - `bucket` (string): Bucket name
1164
+ - `path` (string): File path in the bucket
1165
+
1166
+ **Returns:** Promise\<{data: Buffer, metadata: StorageUploadedFile}>
1167
+
1168
+ **Example:**
1169
+ ```javascript
1170
+ const { data, metadata } = await GoogleCloudStorage.get('my-bucket', 'photos/image.jpg');
1171
+ console.log(metadata.contentType); // 'image/jpeg'
1172
+ console.log(data.length); // File size in bytes
1173
+ ```
1174
+
1175
+ ##### `download(bucket, path)`
1176
+
1177
+ Get a readable stream for a file along with its metadata. Use this for large files or when you need to stream content.
1178
+
1179
+ **Parameters:**
1180
+ - `bucket` (string): Bucket name
1181
+ - `path` (string): File path in the bucket
1182
+
1183
+ **Returns:** Promise\<{stream: Readable, metadata: StorageUploadedFile}>
1184
+
1185
+ **Example:**
1186
+ ```javascript
1187
+ const { stream, metadata } = await GoogleCloudStorage.download('my-bucket', 'videos/large-video.mp4');
1188
+ console.log(metadata.size); // File size
1189
+ stream.pipe(response); // Stream to HTTP response
1190
+ ```
1191
+
1192
+ ##### `getFileStream(bucket, path)`
1193
+
1194
+ Get a readable stream for a file without fetching metadata. Fastest option when metadata is not needed.
1195
+
1196
+ **Parameters:**
1197
+ - `bucket` (string): Bucket name
1198
+ - `path` (string): File path in the bucket
1199
+
1200
+ **Returns:** Readable - Node.js readable stream
1201
+
1202
+ **Example:**
1203
+ ```javascript
1204
+ const stream = GoogleCloudStorage.getFileStream('my-bucket', 'audio/song.mp3');
1205
+ stream.pipe(response); // Direct streaming
1206
+ ```
1207
+
1208
+ ##### `delete(bucket, path)`
1209
+
1210
+ Delete a file from Google Cloud Storage.
1211
+
1212
+ **Parameters:**
1213
+ - `bucket` (string): Bucket name
1214
+ - `path` (string): File path in the bucket
1215
+
1216
+ **Returns:** Promise\<void>
1217
+
1218
+ **Example:**
1219
+ ```javascript
1220
+ await GoogleCloudStorage.delete('my-bucket', 'temp/old-file.txt');
1221
+ ```
1222
+
1223
+ ##### `generateFileDestination(options?)`
1224
+
1225
+ Generate a unique file path with optional directory and extension.
1226
+
1227
+ **Parameters:**
1228
+ - `options` (object, optional):
1229
+ - `directory` (string): Subdirectory path
1230
+ - `mime` (string): MIME type for extension detection
1231
+ - `noExtension` (boolean): Don't append extension
1232
+
1233
+ **Returns:** string - Generated file path
1234
+
1235
+ **Examples:**
1236
+ ```javascript
1237
+ // UUID only
1238
+ GoogleCloudStorage.generateFileDestination();
1239
+ // → 'a1b2c3d4-e5f6-7890-abcd-ef1234567890'
1240
+
1241
+ // With directory and MIME type
1242
+ GoogleCloudStorage.generateFileDestination({
1243
+ directory: 'uploads/images',
1244
+ mime: 'image/jpeg'
1245
+ });
1246
+ // → 'uploads/images/a1b2c3d4-e5f6-7890-abcd-ef1234567890.jpeg'
1247
+
1248
+ // Without extension
1249
+ GoogleCloudStorage.generateFileDestination({
1250
+ directory: 'data',
1251
+ noExtension: true
1252
+ });
1253
+ // → 'data/a1b2c3d4-e5f6-7890-abcd-ef1234567890'
1254
+ ```
1255
+
1256
+ #### Environment Variables
1257
+
1258
+ | Variable | Description | Required |
1259
+ | ------------------------------- | ------------------------------------ | -------- |
1260
+ | GC_PROJECT | Google Cloud Project ID | ✗* |
1261
+ | GOOGLE_APPLICATION_CREDENTIALS | Path to service account key file | ✗* |
1262
+
1263
+ \* Not required if you call `init()` with a config object
1264
+
1265
+ #### Error Handling
1266
+
1267
+ The entity throws `WebError` with appropriate status codes:
1268
+
1269
+ - **413 (Payload Too Large)**: File exceeds `maxSize` limit
1270
+ - Other errors are passed through from Google Cloud Storage SDK
1271
+
1272
+ **Example:**
1273
+ ```javascript
1274
+ try {
1275
+ await GoogleCloudStorage.upload(stream, 'bucket', {}, {}, { maxSize: '1MB' });
1276
+ } catch (error) {
1277
+ if (error.statusCode === 413) {
1278
+ console.log('File too large!');
1279
+ }
1280
+ }
1281
+ ```
1282
+
1283
+ #### Usage in Template Projects
1284
+
1285
+ In your node-server-template endpoints:
1286
+
1287
+ ```javascript
1288
+ import { GoogleCloudStorage } from 'node-server-engine';
1289
+ import { Endpoint, Method } from 'node-server-engine';
1290
+
1291
+ new Endpoint({
1292
+ path: '/upload',
1293
+ method: Method.POST,
1294
+ files: [{ key: 'file', maxSize: '5MB', required: true }],
1295
+ async handler(req, res) {
1296
+ const file = req.files[0];
1297
+ const stream = Readable.from(file.buffer);
1298
+
1299
+ const result = await GoogleCloudStorage.upload(
1300
+ stream,
1301
+ process.env.UPLOAD_BUCKET,
1302
+ { directory: 'user-uploads', mime: file.mimetype }
1303
+ );
1304
+
1305
+ res.json({ path: result.name, url: result.mediaLink });
1306
+ }
1307
+ });
1308
+ ```
701
1309
 
702
1310
  ---
703
1311
 
@@ -980,56 +1588,93 @@ The file's data will be available at `req.body.fileUri req.body.mimeType req.bo
980
1588
 
981
1589
  ### Check Permission Middleware
982
1590
 
983
- /!\ **Must be used in combination with AuthType.JWT**
1591
+ ⚠️ **Must be used in combination with AuthType.JWT**
984
1592
 
985
- Checks if the user has at least one of the required permissions. The permissions are case-insensitive.
1593
+ Role-based access control middleware that checks if the authenticated user has at least one of the required permissions. All permission checks are **case-insensitive** for maximum flexibility.
1594
+
1595
+ #### Features
1596
+
1597
+ - ✅ Single or multiple permission checking
1598
+ - ✅ Case-insensitive permission matching
1599
+ - ✅ Integration with JWT authentication
1600
+ - ✅ Clear error messages for debugging
1601
+ - ✅ TypeScript support
1602
+
1603
+ #### Basic Usage
986
1604
 
987
1605
  ```javascript
988
1606
  import { Endpoint, middleware, AuthType, Method } from 'node-server-engine';
989
1607
 
1608
+ // Single permission check
1609
+ new Endpoint({
1610
+ path: '/users',
1611
+ method: Method.GET,
1612
+ authType: AuthType.JWT,
1613
+ middleware: [middleware.checkPermission('users:read')],
1614
+ handler: (req, res) => {
1615
+ res.json({ message: 'User list' });
1616
+ }
1617
+ });
1618
+
1619
+ // Multiple permissions (user needs at least ONE)
990
1620
  new Endpoint({
991
1621
  path: '/admin',
992
1622
  method: Method.GET,
993
1623
  authType: AuthType.JWT,
994
- middleware: [middleware.checkPermission(['admin', 'superuser'])],
1624
+ middleware: [middleware.checkPermission(['admin', 'superuser', 'moderator'])],
995
1625
  handler: (req, res) => {
996
- res.json({ message: 'Access granted' });
1626
+ res.json({ message: 'Admin access granted' });
997
1627
  }
998
1628
  });
999
1629
  ```
1000
1630
 
1631
+ #### User Object Structure
1632
+
1633
+ The middleware expects `req.user` to contain a `permissions` array:
1634
+
1001
1635
  ```typescript
1002
- import { Request, Response, NextFunction } from 'express';
1636
+ interface User {
1637
+ id: string;
1638
+ permissions: string[]; // e.g., ['users:read', 'users:write', 'admin']
1639
+ }
1640
+ ```
1003
1641
 
1004
- // Middleware to check if the user has at least one of the required permissions (case-insensitive)
1005
- export const checkPermission = (requiredActions: string | string[]) => {
1006
- return (req: Request, res: Response, next: NextFunction) => {
1007
- const user = req.user;
1642
+ #### Examples
1008
1643
 
1009
- if (!user || !user.permissions) {
1010
- return res
1011
- .status(403)
1012
- .json({ message: 'User does not have permissions' });
1013
- }
1644
+ ```javascript
1645
+ // Case-insensitive matching
1646
+ checkPermission('ADMIN') // Matches: 'admin', 'Admin', 'ADMIN'
1647
+ checkPermission(['READ', 'write']) // Matches any case variation
1014
1648
 
1015
- const requiredActionsArray = Array.isArray(requiredActions)
1016
- ? requiredActions
1017
- : [requiredActions];
1649
+ // Namespace-style permissions
1650
+ checkPermission('users:read')
1651
+ checkPermission(['users:write', 'users:delete'])
1018
1652
 
1019
- const requiredActionsLower = requiredActionsArray.map((action) =>
1020
- action.toLowerCase()
1021
- );
1653
+ // Role-based permissions
1654
+ checkPermission(['admin', 'moderator'])
1655
+ ```
1022
1656
 
1023
- const hasPermission = user.permissions.some((permission: string) =>
1024
- requiredActionsLower.includes(permission.toLowerCase())
1025
- );
1657
+ #### Response Codes
1026
1658
 
1027
- if (!hasPermission) {
1028
- return res.status(403).json({ message: 'Permission denied' });
1029
- }
1659
+ - **200**: Permission granted, request proceeds
1660
+ - **403**: Permission denied
1661
+ - No user authenticated
1662
+ - User has no permissions array
1663
+ - User lacks required permission(s)
1664
+
1665
+ #### Error Responses
1030
1666
 
1031
- next();
1032
- };
1667
+ ```json
1668
+ {
1669
+ "message": "User does not have permissions"
1670
+ }
1671
+ ```
1672
+
1673
+ ```json
1674
+ {
1675
+ "message": "Permission denied"
1676
+ }
1677
+ ```
1033
1678
  };
1034
1679
  ```
1035
1680