cyberia 3.0.1 → 3.0.2
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.
- package/.github/workflows/engine-cyberia.cd.yml +1 -0
- package/CHANGELOG.md +56 -1
- package/CLI-HELP.md +2 -4
- package/README.md +139 -0
- package/bin/build.js +5 -0
- package/bin/cyberia.js +385 -71
- package/bin/deploy.js +18 -26
- package/bin/file.js +3 -0
- package/bin/index.js +385 -71
- package/conf.js +32 -3
- package/deployment.yaml +2 -2
- package/manifests/cronjobs/dd-cron/dd-cron-backup.yaml +1 -1
- package/manifests/cronjobs/dd-cron/dd-cron-dns.yaml +1 -1
- package/manifests/deployment/dd-default-development/deployment.yaml +2 -2
- package/manifests/deployment/dd-test-development/deployment.yaml +2 -2
- package/manifests/ipfs/configmap.yaml +7 -0
- package/package.json +8 -8
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.controller.js +2 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.model.js +7 -0
- package/src/api/atlas-sprite-sheet/atlas-sprite-sheet.service.js +93 -2
- package/src/api/file/file.controller.js +3 -13
- package/src/api/file/file.ref.json +0 -21
- package/src/api/ipfs/ipfs.controller.js +104 -0
- package/src/api/ipfs/ipfs.model.js +71 -0
- package/src/api/ipfs/ipfs.router.js +31 -0
- package/src/api/ipfs/ipfs.service.js +193 -0
- package/src/api/object-layer/README.md +139 -0
- package/src/api/object-layer/object-layer.controller.js +3 -0
- package/src/api/object-layer/object-layer.model.js +15 -1
- package/src/api/object-layer/object-layer.router.js +6 -10
- package/src/api/object-layer/object-layer.service.js +311 -182
- package/src/cli/cluster.js +30 -38
- package/src/cli/index.js +0 -1
- package/src/cli/run.js +14 -0
- package/src/client/components/core/LoadingAnimation.js +2 -3
- package/src/client/components/core/Modal.js +1 -1
- package/src/client/components/cyberia/ObjectLayerEngineModal.js +4 -5
- package/src/client/components/cyberia/ObjectLayerEngineViewer.js +280 -29
- package/src/client/services/ipfs/ipfs.service.js +144 -0
- package/src/client/services/object-layer/object-layer.management.js +161 -8
- package/src/index.js +1 -1
- package/src/runtime/express/Express.js +1 -1
- package/src/server/auth.js +18 -18
- package/src/server/ipfs-client.js +433 -0
- package/src/server/object-layer.js +649 -18
- package/src/server/semantic-layer-generator.js +1083 -0
- package/src/server/shape-generator.js +952 -0
- package/test/shape-generator.test.js +457 -0
- package/bin/ssl.js +0 -63
|
@@ -17,7 +17,7 @@ spec:
|
|
|
17
17
|
spec:
|
|
18
18
|
containers:
|
|
19
19
|
- name: dd-default-development-blue
|
|
20
|
-
image: localhost/rockylinux9-underpost:v3.0.
|
|
20
|
+
image: localhost/rockylinux9-underpost:v3.0.2
|
|
21
21
|
# resources:
|
|
22
22
|
# requests:
|
|
23
23
|
# memory: "124Ki"
|
|
@@ -100,7 +100,7 @@ spec:
|
|
|
100
100
|
spec:
|
|
101
101
|
containers:
|
|
102
102
|
- name: dd-default-development-green
|
|
103
|
-
image: localhost/rockylinux9-underpost:v3.0.
|
|
103
|
+
image: localhost/rockylinux9-underpost:v3.0.2
|
|
104
104
|
# resources:
|
|
105
105
|
# requests:
|
|
106
106
|
# memory: "124Ki"
|
|
@@ -18,7 +18,7 @@ spec:
|
|
|
18
18
|
spec:
|
|
19
19
|
containers:
|
|
20
20
|
- name: dd-test-development-blue
|
|
21
|
-
image: localhost/rockylinux9-underpost:v3.0.
|
|
21
|
+
image: localhost/rockylinux9-underpost:v3.0.2
|
|
22
22
|
|
|
23
23
|
command:
|
|
24
24
|
- /bin/sh
|
|
@@ -103,7 +103,7 @@ spec:
|
|
|
103
103
|
spec:
|
|
104
104
|
containers:
|
|
105
105
|
- name: dd-test-development-green
|
|
106
|
-
image: localhost/rockylinux9-underpost:v3.0.
|
|
106
|
+
image: localhost/rockylinux9-underpost:v3.0.2
|
|
107
107
|
|
|
108
108
|
command:
|
|
109
109
|
- /bin/sh
|
|
@@ -16,6 +16,13 @@ data:
|
|
|
16
16
|
ipfs-cluster-service init
|
|
17
17
|
fi
|
|
18
18
|
|
|
19
|
+
# Bind cluster APIs to 0.0.0.0 so they are reachable from other pods.
|
|
20
|
+
# By default ipfs-cluster listens on 127.0.0.1 for REST (9094),
|
|
21
|
+
# Proxy (9095) and Pinning Service (9097).
|
|
22
|
+
sed -i 's|/ip4/127\.0\.0\.1/tcp/9094|/ip4/0.0.0.0/tcp/9094|g' /data/ipfs-cluster/service.json
|
|
23
|
+
sed -i 's|/ip4/127\.0\.0\.1/tcp/9095|/ip4/0.0.0.0/tcp/9095|g' /data/ipfs-cluster/service.json
|
|
24
|
+
sed -i 's|/ip4/127\.0\.0\.1/tcp/9097|/ip4/0.0.0.0/tcp/9097|g' /data/ipfs-cluster/service.json
|
|
25
|
+
|
|
19
26
|
PEER_HOSTNAME=$(cat /proc/sys/kernel/hostname)
|
|
20
27
|
|
|
21
28
|
if echo "${PEER_HOSTNAME}" | grep -q "^ipfs-cluster-0"; then
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"type": "module",
|
|
3
3
|
"main": "src/index.js",
|
|
4
4
|
"name": "cyberia",
|
|
5
|
-
"version": "3.0.
|
|
5
|
+
"version": "3.0.2",
|
|
6
6
|
"description": "Cyberia Engine - Object Layer and Assets Management Microservice",
|
|
7
7
|
"scripts": {
|
|
8
8
|
"start": "env-cmd -f .env.production node --max-old-space-size=8192 src/server",
|
|
@@ -76,20 +76,24 @@
|
|
|
76
76
|
"http-proxy-middleware": "^2.0.6",
|
|
77
77
|
"ignore-walk": "^8.0.0",
|
|
78
78
|
"iovalkey": "^0.2.1",
|
|
79
|
+
"jimp": "^1.6.0",
|
|
79
80
|
"json-colorizer": "^2.2.2",
|
|
80
81
|
"jsonwebtoken": "^9.0.2",
|
|
81
82
|
"mariadb": "^3.2.2",
|
|
82
83
|
"marked": "^12.0.2",
|
|
83
|
-
"
|
|
84
|
+
"maxrects-packer": "^2.7.3",
|
|
85
|
+
"mocha": "^7.2.0",
|
|
84
86
|
"mongoose": "^8.9.5",
|
|
85
87
|
"morgan": "^1.10.0",
|
|
86
88
|
"nodemailer": "^7.0.9",
|
|
87
89
|
"nodemon": "^3.0.1",
|
|
88
90
|
"peer": "^1.0.2",
|
|
89
91
|
"peerjs": "^1.5.2",
|
|
92
|
+
"pngjs": "^7.0.0",
|
|
90
93
|
"prom-client": "^15.1.2",
|
|
91
94
|
"read": "^2.1.0",
|
|
92
95
|
"rrule": "^2.8.1",
|
|
96
|
+
"sharp": "^0.32.5",
|
|
93
97
|
"shelljs": "^0.10.0",
|
|
94
98
|
"sitemap": "^7.1.1",
|
|
95
99
|
"socket.io": "^4.8.0",
|
|
@@ -100,11 +104,7 @@
|
|
|
100
104
|
"uglify-js": "^3.17.4",
|
|
101
105
|
"validator": "^13.11.0",
|
|
102
106
|
"vanilla-jsoneditor": "^2.3.2",
|
|
103
|
-
"winston": "^3.11.0"
|
|
104
|
-
"maxrects-packer": "^2.7.3",
|
|
105
|
-
"pngjs": "^7.0.0",
|
|
106
|
-
"jimp": "^1.6.0",
|
|
107
|
-
"sharp": "^0.32.5"
|
|
107
|
+
"winston": "^3.11.0"
|
|
108
108
|
},
|
|
109
109
|
"publishConfig": {
|
|
110
110
|
"provenance": true,
|
|
@@ -115,4 +115,4 @@
|
|
|
115
115
|
"minimatch": "^10.2.2",
|
|
116
116
|
"glob": "^11.0.0"
|
|
117
117
|
}
|
|
118
|
-
}
|
|
118
|
+
}
|
|
@@ -51,6 +51,8 @@ const AtlasSpriteSheetController = {
|
|
|
51
51
|
},
|
|
52
52
|
get: async (req, res, options) => {
|
|
53
53
|
try {
|
|
54
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
55
|
+
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
|
54
56
|
const { page, limit } = req.query;
|
|
55
57
|
const result = await AtlasSpriteSheetService.get(
|
|
56
58
|
{ ...req, query: { ...req.query, page: parseInt(page), limit: parseInt(limit) } },
|
|
@@ -75,6 +75,7 @@ const DirectionFramesSchema = new Schema(
|
|
|
75
75
|
/**
|
|
76
76
|
* @typedef {Object} AtlasSpriteSheet
|
|
77
77
|
* @property {Types.ObjectId} fileId - Reference to File document (consolidated PNG)
|
|
78
|
+
* @property {string} [cid] - IPFS Content Identifier for the atlas PNG
|
|
78
79
|
* @property {Object} metadata - Atlas sprite sheet metadata
|
|
79
80
|
* @property {string} metadata.itemKey - Item identifier key for texture reference
|
|
80
81
|
* @property {number} metadata.atlasWidth - Total atlas width in pixels
|
|
@@ -92,6 +93,11 @@ const AtlasSpriteSheetSchema = new Schema(
|
|
|
92
93
|
ref: 'File',
|
|
93
94
|
required: true,
|
|
94
95
|
},
|
|
96
|
+
cid: {
|
|
97
|
+
type: String,
|
|
98
|
+
default: '',
|
|
99
|
+
trim: true,
|
|
100
|
+
},
|
|
95
101
|
metadata: {
|
|
96
102
|
itemKey: { type: String, required: true, trim: true },
|
|
97
103
|
atlasWidth: { type: Number, required: true, min: 1 },
|
|
@@ -129,6 +135,7 @@ const AtlasSpriteSheetDto = {
|
|
|
129
135
|
return {
|
|
130
136
|
_id: 1,
|
|
131
137
|
fileId: 1,
|
|
138
|
+
cid: 1,
|
|
132
139
|
metadata: 1,
|
|
133
140
|
createdAt: 1,
|
|
134
141
|
updatedAt: 1,
|
|
@@ -4,6 +4,8 @@ import { DataQuery } from '../../server/data-query.js';
|
|
|
4
4
|
import { AtlasSpriteSheetGenerator } from '../../server/atlas-sprite-sheet-generator.js';
|
|
5
5
|
import { FileFactory } from '../file/file.service.js';
|
|
6
6
|
import { AtlasSpriteSheetDto } from './atlas-sprite-sheet.model.js';
|
|
7
|
+
import { IpfsClient } from '../../server/ipfs-client.js';
|
|
8
|
+
import { createPinRecord, removePinRecordsAndUnpin } from '../ipfs/ipfs.service.js';
|
|
7
9
|
|
|
8
10
|
const logger = loggerFactory(import.meta);
|
|
9
11
|
|
|
@@ -39,6 +41,27 @@ const AtlasSpriteSheetService = {
|
|
|
39
41
|
|
|
40
42
|
const fileDoc = await new File(FileFactory.create(buffer, `${itemKey}.png`)).save();
|
|
41
43
|
|
|
44
|
+
// Add atlas PNG to IPFS and obtain its CID
|
|
45
|
+
let atlasCid = '';
|
|
46
|
+
try {
|
|
47
|
+
const ipfsResult = await IpfsClient.addBufferToIpfs(
|
|
48
|
+
buffer,
|
|
49
|
+
`${itemKey}_atlas_sprite_sheet.png`,
|
|
50
|
+
`/object-layer/${itemKey}/${itemKey}_atlas_sprite_sheet.png`,
|
|
51
|
+
);
|
|
52
|
+
if (ipfsResult) {
|
|
53
|
+
atlasCid = ipfsResult.cid;
|
|
54
|
+
// Create pin record for the authenticated user (when available)
|
|
55
|
+
const userId = req.auth && req.auth.user ? req.auth.user._id : undefined;
|
|
56
|
+
if (userId) {
|
|
57
|
+
await createPinRecord({ cid: atlasCid, userId, options });
|
|
58
|
+
}
|
|
59
|
+
logger.info(`Atlas sprite sheet pinned to IPFS – CID: ${atlasCid}`);
|
|
60
|
+
}
|
|
61
|
+
} catch (ipfsError) {
|
|
62
|
+
logger.warn('Failed to add atlas sprite sheet to IPFS:', ipfsError.message);
|
|
63
|
+
}
|
|
64
|
+
|
|
42
65
|
let atlasDoc = await AtlasSpriteSheet.findOne({ 'metadata.itemKey': itemKey });
|
|
43
66
|
|
|
44
67
|
if (atlasDoc) {
|
|
@@ -47,16 +70,20 @@ const AtlasSpriteSheetService = {
|
|
|
47
70
|
await File.findByIdAndDelete(atlasDoc.fileId);
|
|
48
71
|
}
|
|
49
72
|
atlasDoc.fileId = fileDoc._id;
|
|
73
|
+
atlasDoc.cid = atlasCid;
|
|
50
74
|
atlasDoc.metadata = metadata;
|
|
51
75
|
await atlasDoc.save();
|
|
52
76
|
} else {
|
|
53
77
|
atlasDoc = await new AtlasSpriteSheet({
|
|
54
78
|
fileId: fileDoc._id,
|
|
79
|
+
cid: atlasCid,
|
|
55
80
|
metadata,
|
|
56
81
|
}).save();
|
|
57
82
|
}
|
|
58
83
|
|
|
59
84
|
objectLayer.atlasSpriteSheetId = atlasDoc._id;
|
|
85
|
+
objectLayer.data.atlasSpriteSheetCid = atlasCid;
|
|
86
|
+
objectLayer.markModified('data.atlasSpriteSheetCid');
|
|
60
87
|
await objectLayer.save();
|
|
61
88
|
|
|
62
89
|
return atlasDoc;
|
|
@@ -78,12 +105,30 @@ const AtlasSpriteSheetService = {
|
|
|
78
105
|
if (objectLayer.atlasSpriteSheetId) {
|
|
79
106
|
const atlasDoc = await AtlasSpriteSheet.findById(objectLayer.atlasSpriteSheetId);
|
|
80
107
|
if (atlasDoc) {
|
|
108
|
+
// Remove pin records and unpin atlas CID from IPFS
|
|
109
|
+
const atlasCid = atlasDoc.cid || objectLayer.data.atlasSpriteSheetCid;
|
|
110
|
+
if (atlasCid) {
|
|
111
|
+
try {
|
|
112
|
+
await removePinRecordsAndUnpin(atlasCid, options);
|
|
113
|
+
// Remove the MFS entry for the atlas sprite sheet PNG
|
|
114
|
+
const itemId = objectLayer.data.item.id;
|
|
115
|
+
await IpfsClient.removeMfsPath(`/object-layer/${itemId}/${itemId}_atlas_sprite_sheet.png`);
|
|
116
|
+
logger.info(`Cleaned up IPFS atlas CID ${atlasCid} for ObjectLayer ${objectLayer._id}`);
|
|
117
|
+
} catch (ipfsErr) {
|
|
118
|
+
logger.warn(`Failed to clean up IPFS atlas CID ${atlasCid}: ${ipfsErr.message}`);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// Delete the atlas File document from MongoDB
|
|
81
123
|
if (atlasDoc.fileId) {
|
|
82
124
|
await File.findByIdAndDelete(atlasDoc.fileId);
|
|
83
125
|
}
|
|
126
|
+
// Delete the AtlasSpriteSheet document itself
|
|
84
127
|
await AtlasSpriteSheet.findByIdAndDelete(atlasDoc._id);
|
|
85
128
|
}
|
|
86
129
|
objectLayer.atlasSpriteSheetId = undefined;
|
|
130
|
+
objectLayer.data.atlasSpriteSheetCid = '';
|
|
131
|
+
objectLayer.markModified('data.atlasSpriteSheetCid');
|
|
87
132
|
await objectLayer.save();
|
|
88
133
|
}
|
|
89
134
|
|
|
@@ -130,8 +175,54 @@ const AtlasSpriteSheetService = {
|
|
|
130
175
|
/** @type {import('./atlas-sprite-sheet.model.js').AtlasSpriteSheetModel} */
|
|
131
176
|
const AtlasSpriteSheet =
|
|
132
177
|
DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.AtlasSpriteSheet;
|
|
133
|
-
|
|
134
|
-
|
|
178
|
+
/** @type {import('../file/file.model.js').FileModel} */
|
|
179
|
+
const File = DataBaseProvider.instance[`${options.host}${options.path}`].mongoose.models.File;
|
|
180
|
+
|
|
181
|
+
if (req.params.id) {
|
|
182
|
+
const atlasDoc = await AtlasSpriteSheet.findById(req.params.id);
|
|
183
|
+
if (!atlasDoc) return null;
|
|
184
|
+
|
|
185
|
+
// Remove pin records and unpin atlas CID from IPFS
|
|
186
|
+
if (atlasDoc.cid) {
|
|
187
|
+
try {
|
|
188
|
+
await removePinRecordsAndUnpin(atlasDoc.cid, options);
|
|
189
|
+
if (atlasDoc.metadata?.itemKey) {
|
|
190
|
+
const itemKey = atlasDoc.metadata.itemKey;
|
|
191
|
+
await IpfsClient.removeMfsPath(`/object-layer/${itemKey}/${itemKey}_atlas_sprite_sheet.png`);
|
|
192
|
+
}
|
|
193
|
+
logger.info(`Cleaned up IPFS atlas CID ${atlasDoc.cid} for AtlasSpriteSheet ${atlasDoc._id}`);
|
|
194
|
+
} catch (ipfsErr) {
|
|
195
|
+
logger.warn(`Failed to clean up IPFS atlas CID ${atlasDoc.cid}: ${ipfsErr.message}`);
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// Delete the referenced File document (the atlas PNG blob)
|
|
200
|
+
if (atlasDoc.fileId) {
|
|
201
|
+
await File.findByIdAndDelete(atlasDoc.fileId);
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
return await AtlasSpriteSheet.findByIdAndDelete(req.params.id);
|
|
205
|
+
} else {
|
|
206
|
+
// Bulk delete: iterate each atlas to clean up File, IPFS pins, and pin records
|
|
207
|
+
const allAtlases = await AtlasSpriteSheet.find({});
|
|
208
|
+
for (const atlasDoc of allAtlases) {
|
|
209
|
+
try {
|
|
210
|
+
if (atlasDoc.cid) {
|
|
211
|
+
await removePinRecordsAndUnpin(atlasDoc.cid, options);
|
|
212
|
+
if (atlasDoc.metadata?.itemKey) {
|
|
213
|
+
const itemKey = atlasDoc.metadata.itemKey;
|
|
214
|
+
await IpfsClient.removeMfsPath(`/object-layer/${itemKey}/${itemKey}_atlas_sprite_sheet.png`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
if (atlasDoc.fileId) {
|
|
218
|
+
await File.findByIdAndDelete(atlasDoc.fileId);
|
|
219
|
+
}
|
|
220
|
+
} catch (err) {
|
|
221
|
+
logger.error(`Failed to clean up AtlasSpriteSheet ${atlasDoc._id} during bulk delete: ${err.message}`);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
return await AtlasSpriteSheet.deleteMany();
|
|
225
|
+
}
|
|
135
226
|
},
|
|
136
227
|
};
|
|
137
228
|
|
|
@@ -19,22 +19,12 @@ const FileController = {
|
|
|
19
19
|
},
|
|
20
20
|
get: async (req, res, options) => {
|
|
21
21
|
try {
|
|
22
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
23
|
+
res.setHeader('Cross-Origin-Resource-Policy', 'cross-origin');
|
|
22
24
|
const result = await FileService.get(req, res, options);
|
|
23
25
|
if (result instanceof Buffer) {
|
|
24
|
-
|
|
25
|
-
process.env.NODE_ENV === 'development' ||
|
|
26
|
-
req.hostname === options.host ||
|
|
27
|
-
(options.origins && options.origins.find((o) => o.match(req.hostname)))
|
|
28
|
-
) {
|
|
29
|
-
res.set('Cross-Origin-Resource-Policy', 'cross-origin');
|
|
30
|
-
return res.status(200).end(result);
|
|
31
|
-
}
|
|
32
|
-
return res.status(403).json({
|
|
33
|
-
status: 'error',
|
|
34
|
-
message: 'Forbidden',
|
|
35
|
-
});
|
|
26
|
+
return res.status(200).end(result);
|
|
36
27
|
}
|
|
37
|
-
|
|
38
28
|
return res.status(200).json({
|
|
39
29
|
status: 'success',
|
|
40
30
|
data: result,
|
|
@@ -5,27 +5,6 @@
|
|
|
5
5
|
"logo": true
|
|
6
6
|
}
|
|
7
7
|
},
|
|
8
|
-
{
|
|
9
|
-
"api": "cyberia-biome",
|
|
10
|
-
"model": {
|
|
11
|
-
"fileId": true,
|
|
12
|
-
"topLevelColorFileId": true
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
{
|
|
16
|
-
"api": "cyberia-tile",
|
|
17
|
-
"model": {
|
|
18
|
-
"fileId": true
|
|
19
|
-
}
|
|
20
|
-
},
|
|
21
|
-
{
|
|
22
|
-
"api": "cyberia-world",
|
|
23
|
-
"model": {
|
|
24
|
-
"adjacentFace": {
|
|
25
|
-
"fileId": true
|
|
26
|
-
}
|
|
27
|
-
}
|
|
28
|
-
},
|
|
29
8
|
{
|
|
30
9
|
"api": "document",
|
|
31
10
|
"model": {
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { loggerFactory } from '../../server/logger.js';
|
|
2
|
+
import { IpfsService } from './ipfs.service.js';
|
|
3
|
+
|
|
4
|
+
const logger = loggerFactory(import.meta);
|
|
5
|
+
|
|
6
|
+
const IpfsController = {
|
|
7
|
+
post: async (req, res, options) => {
|
|
8
|
+
try {
|
|
9
|
+
const result = await IpfsService.post(req, res, options);
|
|
10
|
+
return res.status(200).json({
|
|
11
|
+
status: 'success',
|
|
12
|
+
data: result,
|
|
13
|
+
});
|
|
14
|
+
} catch (error) {
|
|
15
|
+
logger.error(error, error.stack);
|
|
16
|
+
return res.status(400).json({
|
|
17
|
+
status: 'error',
|
|
18
|
+
message: error.message,
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
get: async (req, res, options) => {
|
|
23
|
+
try {
|
|
24
|
+
const { page, limit } = req.query;
|
|
25
|
+
const result = await IpfsService.get(
|
|
26
|
+
{ ...req, query: { ...req.query, page: parseInt(page), limit: parseInt(limit) } },
|
|
27
|
+
res,
|
|
28
|
+
options,
|
|
29
|
+
);
|
|
30
|
+
return res.status(200).json({
|
|
31
|
+
status: 'success',
|
|
32
|
+
data: result,
|
|
33
|
+
});
|
|
34
|
+
} catch (error) {
|
|
35
|
+
logger.error(error, error.stack);
|
|
36
|
+
return res.status(400).json({
|
|
37
|
+
status: 'error',
|
|
38
|
+
message: error.message,
|
|
39
|
+
});
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
put: async (req, res, options) => {
|
|
43
|
+
try {
|
|
44
|
+
const result = await IpfsService.put(req, res, options);
|
|
45
|
+
return res.status(200).json({
|
|
46
|
+
status: 'success',
|
|
47
|
+
data: result,
|
|
48
|
+
});
|
|
49
|
+
} catch (error) {
|
|
50
|
+
logger.error(error, error.stack);
|
|
51
|
+
return res.status(400).json({
|
|
52
|
+
status: 'error',
|
|
53
|
+
message: error.message,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
delete: async (req, res, options) => {
|
|
58
|
+
try {
|
|
59
|
+
const result = await IpfsService.delete(req, res, options);
|
|
60
|
+
return res.status(200).json({
|
|
61
|
+
status: 'success',
|
|
62
|
+
data: result,
|
|
63
|
+
});
|
|
64
|
+
} catch (error) {
|
|
65
|
+
logger.error(error, error.stack);
|
|
66
|
+
return res.status(400).json({
|
|
67
|
+
status: 'error',
|
|
68
|
+
message: error.message,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
pin: async (req, res, options) => {
|
|
73
|
+
try {
|
|
74
|
+
const result = await IpfsService.pin(req, res, options);
|
|
75
|
+
return res.status(200).json({
|
|
76
|
+
status: 'success',
|
|
77
|
+
data: result,
|
|
78
|
+
});
|
|
79
|
+
} catch (error) {
|
|
80
|
+
logger.error(error, error.stack);
|
|
81
|
+
return res.status(400).json({
|
|
82
|
+
status: 'error',
|
|
83
|
+
message: error.message,
|
|
84
|
+
});
|
|
85
|
+
}
|
|
86
|
+
},
|
|
87
|
+
unpin: async (req, res, options) => {
|
|
88
|
+
try {
|
|
89
|
+
const result = await IpfsService.unpin(req, res, options);
|
|
90
|
+
return res.status(200).json({
|
|
91
|
+
status: 'success',
|
|
92
|
+
data: result,
|
|
93
|
+
});
|
|
94
|
+
} catch (error) {
|
|
95
|
+
logger.error(error, error.stack);
|
|
96
|
+
return res.status(400).json({
|
|
97
|
+
status: 'error',
|
|
98
|
+
message: error.message,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
export { IpfsController };
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mongoose model for IPFS API – a general-purpose pin record that relates
|
|
3
|
+
* a user to a CID.
|
|
4
|
+
*
|
|
5
|
+
* @module src/api/ipfs/ipfs.model.js
|
|
6
|
+
* @namespace IpfsModel
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Schema, model } from 'mongoose';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @typedef {Object} Ipfs
|
|
13
|
+
* @property {string} cid – IPFS Content Identifier (CIDv0 or CIDv1).
|
|
14
|
+
* @property {Types.ObjectId} userId – Reference to the User who owns / requested the pin.
|
|
15
|
+
* @property {Date} createdAt – Auto-managed by Mongoose.
|
|
16
|
+
* @property {Date} updatedAt – Auto-managed by Mongoose.
|
|
17
|
+
* @memberof IpfsModel
|
|
18
|
+
*/
|
|
19
|
+
const IpfsSchema = new Schema(
|
|
20
|
+
{
|
|
21
|
+
cid: {
|
|
22
|
+
type: String,
|
|
23
|
+
required: true,
|
|
24
|
+
trim: true,
|
|
25
|
+
},
|
|
26
|
+
userId: {
|
|
27
|
+
type: Schema.Types.ObjectId,
|
|
28
|
+
ref: 'User',
|
|
29
|
+
required: true,
|
|
30
|
+
},
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
timestamps: true,
|
|
34
|
+
},
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
// Compound index: one pin record per user + CID pair.
|
|
38
|
+
IpfsSchema.index({ cid: 1, userId: 1 }, { unique: true });
|
|
39
|
+
|
|
40
|
+
// Fast look-ups by user.
|
|
41
|
+
IpfsSchema.index({ userId: 1 });
|
|
42
|
+
|
|
43
|
+
// Fast look-ups by CID.
|
|
44
|
+
IpfsSchema.index({ cid: 1 });
|
|
45
|
+
|
|
46
|
+
const IpfsModel = model('Ipfs', IpfsSchema);
|
|
47
|
+
|
|
48
|
+
const ProviderSchema = IpfsSchema;
|
|
49
|
+
|
|
50
|
+
const IpfsDto = {
|
|
51
|
+
select: {
|
|
52
|
+
get: () => {
|
|
53
|
+
return {
|
|
54
|
+
_id: 1,
|
|
55
|
+
cid: 1,
|
|
56
|
+
userId: 1,
|
|
57
|
+
createdAt: 1,
|
|
58
|
+
updatedAt: 1,
|
|
59
|
+
};
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
populate: {
|
|
63
|
+
user: () => ({
|
|
64
|
+
path: 'userId',
|
|
65
|
+
model: 'User',
|
|
66
|
+
select: '_id username email role',
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
export { IpfsSchema, IpfsModel, ProviderSchema, IpfsDto };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { adminGuard } from '../../server/auth.js';
|
|
2
|
+
import { loggerFactory } from '../../server/logger.js';
|
|
3
|
+
import { IpfsController } from './ipfs.controller.js';
|
|
4
|
+
import express from 'express';
|
|
5
|
+
|
|
6
|
+
const logger = loggerFactory(import.meta);
|
|
7
|
+
|
|
8
|
+
const IpfsRouter = (options) => {
|
|
9
|
+
const router = express.Router();
|
|
10
|
+
const authMiddleware = options.authMiddleware;
|
|
11
|
+
router.post(`/pin`, authMiddleware, adminGuard, async (req, res) => await IpfsController.pin(req, res, options));
|
|
12
|
+
router.delete(
|
|
13
|
+
`/pin/:cid`,
|
|
14
|
+
authMiddleware,
|
|
15
|
+
adminGuard,
|
|
16
|
+
async (req, res) => await IpfsController.unpin(req, res, options),
|
|
17
|
+
);
|
|
18
|
+
router.post(`/:id`, authMiddleware, async (req, res) => await IpfsController.post(req, res, options));
|
|
19
|
+
router.post(`/`, authMiddleware, async (req, res) => await IpfsController.post(req, res, options));
|
|
20
|
+
router.get(`/:id`, authMiddleware, async (req, res) => await IpfsController.get(req, res, options));
|
|
21
|
+
router.get(`/`, authMiddleware, async (req, res) => await IpfsController.get(req, res, options));
|
|
22
|
+
router.put(`/:id`, authMiddleware, async (req, res) => await IpfsController.put(req, res, options));
|
|
23
|
+
router.put(`/`, authMiddleware, async (req, res) => await IpfsController.put(req, res, options));
|
|
24
|
+
router.delete(`/:id`, authMiddleware, adminGuard, async (req, res) => await IpfsController.delete(req, res, options));
|
|
25
|
+
router.delete(`/`, authMiddleware, adminGuard, async (req, res) => await IpfsController.delete(req, res, options));
|
|
26
|
+
return router;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
const ApiRouter = IpfsRouter;
|
|
30
|
+
|
|
31
|
+
export { ApiRouter, IpfsRouter };
|