@tomorrowos/sdk 0.5.2 → 0.5.5
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/dist/cloudinary-storage.d.ts +20 -0
- package/dist/cloudinary-storage.d.ts.map +1 -0
- package/dist/cloudinary-storage.js +70 -0
- package/dist/store/memory-store.d.ts +6 -1
- package/dist/store/memory-store.d.ts.map +1 -1
- package/dist/store/memory-store.js +19 -0
- package/dist/store/migration.d.ts +1 -0
- package/dist/store/migration.d.ts.map +1 -1
- package/dist/store/migration.js +8 -2
- package/dist/store/postgres-store.d.ts +7 -1
- package/dist/store/postgres-store.d.ts.map +1 -1
- package/dist/store/postgres-store.js +109 -0
- package/dist/store/sqlite-store.d.ts +7 -1
- package/dist/store/sqlite-store.d.ts.map +1 -1
- package/dist/store/sqlite-store.js +90 -0
- package/dist/store/types.d.ts +21 -0
- package/dist/store/types.d.ts.map +1 -1
- package/dist/tomorrowos.d.ts +5 -0
- package/dist/tomorrowos.d.ts.map +1 -1
- package/dist/tomorrowos.js +151 -13
- package/package.json +3 -2
- package/templates/cms-starter/package.json +2 -2
- package/templates/cms-starter/public/methods.js +4 -1
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export interface CloudinaryStorageConfig {
|
|
2
|
+
cloudName: string;
|
|
3
|
+
apiKey: string;
|
|
4
|
+
apiSecret: string;
|
|
5
|
+
folder?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface CloudinaryUploadResult {
|
|
8
|
+
publicId: string;
|
|
9
|
+
secureUrl: string;
|
|
10
|
+
resourceType: string;
|
|
11
|
+
bytes?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare function resolveCloudinaryConfig(env?: NodeJS.ProcessEnv): CloudinaryStorageConfig | null;
|
|
14
|
+
export declare function uploadBufferToCloudinary(body: Buffer, options: {
|
|
15
|
+
config: CloudinaryStorageConfig;
|
|
16
|
+
publicId: string;
|
|
17
|
+
filename: string;
|
|
18
|
+
}): Promise<CloudinaryUploadResult>;
|
|
19
|
+
export declare function deleteCloudinaryAsset(config: CloudinaryStorageConfig, publicId: string, resourceType?: string): Promise<void>;
|
|
20
|
+
//# sourceMappingURL=cloudinary-storage.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cloudinary-storage.d.ts","sourceRoot":"","sources":["../src/cloudinary-storage.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,uBAAuB;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAOD,wBAAgB,uBAAuB,CACrC,GAAG,GAAE,MAAM,CAAC,UAAwB,GACnC,uBAAuB,GAAG,IAAI,CAoBhC;AAWD,wBAAsB,wBAAwB,CAC5C,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE;IACP,MAAM,EAAE,uBAAuB,CAAC;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACA,OAAO,CAAC,sBAAsB,CAAC,CAsCjC;AAED,wBAAsB,qBAAqB,CACzC,MAAM,EAAE,uBAAuB,EAC/B,QAAQ,EAAE,MAAM,EAChB,YAAY,SAAU,GACrB,OAAO,CAAC,IAAI,CAAC,CAKf"}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { v2 as cloudinary } from "cloudinary";
|
|
2
|
+
function cleanOptional(value) {
|
|
3
|
+
const trimmed = String(value || "").trim();
|
|
4
|
+
return trimmed || undefined;
|
|
5
|
+
}
|
|
6
|
+
export function resolveCloudinaryConfig(env = process.env) {
|
|
7
|
+
const cloudName = cleanOptional(env.CLOUDINARY_CLOUD_NAME);
|
|
8
|
+
const apiKey = cleanOptional(env.CLOUDINARY_API_KEY);
|
|
9
|
+
const apiSecret = cleanOptional(env.CLOUDINARY_API_SECRET);
|
|
10
|
+
const hasAny = !!(cloudName || apiKey || apiSecret);
|
|
11
|
+
if (!hasAny)
|
|
12
|
+
return null;
|
|
13
|
+
if (!cloudName || !apiKey || !apiSecret) {
|
|
14
|
+
console.warn("[TomorrowOS] incomplete Cloudinary config; falling back to local uploads.");
|
|
15
|
+
return null;
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
cloudName,
|
|
19
|
+
apiKey,
|
|
20
|
+
apiSecret,
|
|
21
|
+
folder: cleanOptional(env.CLOUDINARY_FOLDER)
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
function configureCloudinary(config) {
|
|
25
|
+
cloudinary.config({
|
|
26
|
+
cloud_name: config.cloudName,
|
|
27
|
+
api_key: config.apiKey,
|
|
28
|
+
api_secret: config.apiSecret,
|
|
29
|
+
secure: true
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
export async function uploadBufferToCloudinary(body, options) {
|
|
33
|
+
configureCloudinary(options.config);
|
|
34
|
+
const publicId = options.config.folder
|
|
35
|
+
? `${options.config.folder.replace(/^\/+|\/+$/g, "")}/${options.publicId}`
|
|
36
|
+
: options.publicId;
|
|
37
|
+
const result = await new Promise((resolve, reject) => {
|
|
38
|
+
const stream = cloudinary.uploader.upload_stream({
|
|
39
|
+
resource_type: "auto",
|
|
40
|
+
public_id: publicId,
|
|
41
|
+
overwrite: true,
|
|
42
|
+
unique_filename: false,
|
|
43
|
+
use_filename: false,
|
|
44
|
+
filename_override: options.filename
|
|
45
|
+
}, (err, uploadResult) => {
|
|
46
|
+
if (err) {
|
|
47
|
+
reject(err);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (!uploadResult) {
|
|
51
|
+
reject(new Error("Cloudinary upload returned no result"));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
resolve(uploadResult);
|
|
55
|
+
});
|
|
56
|
+
stream.end(body);
|
|
57
|
+
});
|
|
58
|
+
return {
|
|
59
|
+
publicId: result.public_id,
|
|
60
|
+
secureUrl: result.secure_url,
|
|
61
|
+
resourceType: result.resource_type,
|
|
62
|
+
bytes: result.bytes
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export async function deleteCloudinaryAsset(config, publicId, resourceType = "image") {
|
|
66
|
+
configureCloudinary(config);
|
|
67
|
+
await cloudinary.uploader.destroy(publicId, {
|
|
68
|
+
resource_type: resourceType
|
|
69
|
+
});
|
|
70
|
+
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DevicePlaylistAssignment, DeviceRegistryRecord, PairedDeviceEntry, PairedDeviceRecord, PendingCodeRecord, StoredPlaylist, TomorrowOSStore } from "./types.js";
|
|
1
|
+
import type { DevicePlaylistAssignment, DeviceRegistryRecord, PairedDeviceEntry, PairedDeviceRecord, PendingCodeRecord, StoredPlaylist, TomorrowOSStore, UploadedAssetRecord } from "./types.js";
|
|
2
2
|
/**
|
|
3
3
|
* Default store for development / single-node demos.
|
|
4
4
|
* Data is lost on process restart — not for multi-instance production.
|
|
@@ -9,6 +9,7 @@ export declare class MemoryStore implements TomorrowOSStore {
|
|
|
9
9
|
private readonly codeToDeviceId;
|
|
10
10
|
private readonly pairedDevices;
|
|
11
11
|
private readonly playlists;
|
|
12
|
+
private readonly uploadedAssets;
|
|
12
13
|
private readonly deviceAssignments;
|
|
13
14
|
setPendingCode(code: string, record: PendingCodeRecord): Promise<void>;
|
|
14
15
|
getPendingCode(code: string): Promise<PendingCodeRecord | undefined>;
|
|
@@ -27,6 +28,10 @@ export declare class MemoryStore implements TomorrowOSStore {
|
|
|
27
28
|
getPlaylist(id: string): Promise<StoredPlaylist | undefined>;
|
|
28
29
|
setPlaylist(record: StoredPlaylist): Promise<void>;
|
|
29
30
|
isPlaylistNameTaken(name: string, excludeId?: string): Promise<boolean>;
|
|
31
|
+
getUploadedAsset(id: string): Promise<UploadedAssetRecord | undefined>;
|
|
32
|
+
getUploadedAssetBySha256(sha256: string, storageProvider?: UploadedAssetRecord["storageProvider"]): Promise<UploadedAssetRecord | undefined>;
|
|
33
|
+
setUploadedAsset(record: UploadedAssetRecord): Promise<void>;
|
|
34
|
+
deleteUploadedAsset(id: string): Promise<void>;
|
|
30
35
|
getDeviceAssignments(deviceId: string): Promise<DevicePlaylistAssignment[]>;
|
|
31
36
|
setDeviceAssignments(deviceId: string, assignments: DevicePlaylistAssignment[]): Promise<void>;
|
|
32
37
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../../src/store/memory-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,eAAe,
|
|
1
|
+
{"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../../src/store/memory-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,qBAAa,WAAY,YAAW,eAAe;IACjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAwC;IACrE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA2C;IAC1E,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyC;IACvE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqC;IAC/D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA0C;IACzE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAiD;IAE7E,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAIpE,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,iBAAiB,CACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC;IAItC,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC;IASV,uBAAuB,CAC3B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,oBAAoB,CAAA;KAAE,GAAG,SAAS,CAAC;IAQpE,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,IAAI,CAAC;IAIV,eAAe,CACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAIpC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD,iBAAiB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAOjD,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAI1C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAI5D,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUvE,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAItE,wBAAwB,CAC5B,MAAM,EAAE,MAAM,EACd,eAAe,CAAC,EAAE,mBAAmB,CAAC,iBAAiB,CAAC,GACvD,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAYrC,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5D,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,oBAAoB,CACxB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAIhC,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,wBAAwB,EAAE,GACtC,OAAO,CAAC,IAAI,CAAC;CASjB"}
|
|
@@ -8,6 +8,7 @@ export class MemoryStore {
|
|
|
8
8
|
codeToDeviceId = new Map();
|
|
9
9
|
pairedDevices = new Map();
|
|
10
10
|
playlists = new Map();
|
|
11
|
+
uploadedAssets = new Map();
|
|
11
12
|
deviceAssignments = new Map();
|
|
12
13
|
async setPendingCode(code, record) {
|
|
13
14
|
this.pendingCodes.set(code, record);
|
|
@@ -74,6 +75,24 @@ export class MemoryStore {
|
|
|
74
75
|
}
|
|
75
76
|
return false;
|
|
76
77
|
}
|
|
78
|
+
async getUploadedAsset(id) {
|
|
79
|
+
return this.uploadedAssets.get(id);
|
|
80
|
+
}
|
|
81
|
+
async getUploadedAssetBySha256(sha256, storageProvider) {
|
|
82
|
+
for (const asset of this.uploadedAssets.values()) {
|
|
83
|
+
if (asset.sha256 === sha256 &&
|
|
84
|
+
(!storageProvider || asset.storageProvider === storageProvider)) {
|
|
85
|
+
return asset;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return undefined;
|
|
89
|
+
}
|
|
90
|
+
async setUploadedAsset(record) {
|
|
91
|
+
this.uploadedAssets.set(record.id, record);
|
|
92
|
+
}
|
|
93
|
+
async deleteUploadedAsset(id) {
|
|
94
|
+
this.uploadedAssets.delete(id);
|
|
95
|
+
}
|
|
77
96
|
async getDeviceAssignments(deviceId) {
|
|
78
97
|
return [...(this.deviceAssignments.get(deviceId) ?? [])];
|
|
79
98
|
}
|
|
@@ -4,6 +4,7 @@ export interface MigrationResult {
|
|
|
4
4
|
deviceRegistry: number;
|
|
5
5
|
pairedDevices: number;
|
|
6
6
|
playlists: number;
|
|
7
|
+
uploadedAssets: number;
|
|
7
8
|
deviceAssignments: number;
|
|
8
9
|
}
|
|
9
10
|
export declare function exportTomorrowOSData(store: TomorrowOSMigratableStore): Promise<TomorrowOSDataSnapshot>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../../src/store/migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,sBAAsB,CAAC,
|
|
1
|
+
{"version":3,"file":"migration.d.ts","sourceRoot":"","sources":["../../src/store/migration.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,sBAAsB,EACtB,yBAAyB,EAC1B,MAAM,YAAY,CAAC;AAEpB,MAAM,WAAW,eAAe;IAC9B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,SAAS,EAAE,MAAM,CAAC;IAClB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,GAC/B,OAAO,CAAC,sBAAsB,CAAC,CA8BjC;AAED,wBAAsB,oBAAoB,CACxC,KAAK,EAAE,yBAAyB,EAChC,QAAQ,EAAE,sBAAsB,GAC/B,OAAO,CAAC,eAAe,CAAC,CAoC1B;AAED,wBAAsB,qBAAqB,CACzC,IAAI,EAAE,yBAAyB,EAC/B,EAAE,EAAE,yBAAyB,GAC5B,OAAO,CAAC,eAAe,CAAC,CAG1B"}
|
package/dist/store/migration.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
export async function exportTomorrowOSData(store) {
|
|
2
|
-
const [pendingCodes, deviceRegistry, pairedDevices, playlists] = await Promise.all([
|
|
2
|
+
const [pendingCodes, deviceRegistry, pairedDevices, playlists, uploadedAssets] = await Promise.all([
|
|
3
3
|
store.listPendingCodes(),
|
|
4
4
|
store.listDeviceRegistry(),
|
|
5
5
|
store.listPairedDevices(),
|
|
6
|
-
store.listPlaylists()
|
|
6
|
+
store.listPlaylists(),
|
|
7
|
+
store.listUploadedAssets()
|
|
7
8
|
]);
|
|
8
9
|
const deviceAssignments = await Promise.all(pairedDevices.map(async ({ deviceId }) => ({
|
|
9
10
|
deviceId,
|
|
@@ -14,6 +15,7 @@ export async function exportTomorrowOSData(store) {
|
|
|
14
15
|
deviceRegistry,
|
|
15
16
|
pairedDevices,
|
|
16
17
|
playlists,
|
|
18
|
+
uploadedAssets,
|
|
17
19
|
deviceAssignments
|
|
18
20
|
};
|
|
19
21
|
}
|
|
@@ -27,6 +29,9 @@ export async function importTomorrowOSData(store, snapshot) {
|
|
|
27
29
|
for (const playlist of snapshot.playlists) {
|
|
28
30
|
await store.setPlaylist(playlist);
|
|
29
31
|
}
|
|
32
|
+
for (const asset of snapshot.uploadedAssets ?? []) {
|
|
33
|
+
await store.setUploadedAsset(asset);
|
|
34
|
+
}
|
|
30
35
|
for (const { deviceId, assignments } of snapshot.deviceAssignments) {
|
|
31
36
|
await store.setDeviceAssignments(deviceId, assignments);
|
|
32
37
|
}
|
|
@@ -38,6 +43,7 @@ export async function importTomorrowOSData(store, snapshot) {
|
|
|
38
43
|
deviceRegistry: snapshot.deviceRegistry.length,
|
|
39
44
|
pairedDevices: snapshot.pairedDevices.length,
|
|
40
45
|
playlists: snapshot.playlists.length,
|
|
46
|
+
uploadedAssets: snapshot.uploadedAssets?.length ?? 0,
|
|
41
47
|
deviceAssignments: snapshot.deviceAssignments.reduce((total, entry) => total + entry.assignments.length, 0)
|
|
42
48
|
};
|
|
43
49
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type PoolConfig } from "pg";
|
|
2
|
-
import type { DevicePlaylistAssignment, DeviceRegistryEntry, DeviceRegistryRecord, PairedDeviceEntry, PairedDeviceRecord, PendingCodeEntry, PendingCodeRecord, StoredPlaylist, TomorrowOSMigratableStore } from "./types.js";
|
|
2
|
+
import type { DevicePlaylistAssignment, DeviceRegistryEntry, DeviceRegistryRecord, PairedDeviceEntry, PairedDeviceRecord, PendingCodeEntry, PendingCodeRecord, StoredPlaylist, TomorrowOSMigratableStore, UploadedAssetRecord, UploadedAssetStorageProvider } from "./types.js";
|
|
3
3
|
export interface PostgresStoreOptions {
|
|
4
4
|
connectionString: string;
|
|
5
5
|
ssl?: PoolConfig["ssl"];
|
|
@@ -34,10 +34,16 @@ export declare class PostgresStore implements TomorrowOSMigratableStore {
|
|
|
34
34
|
getPlaylist(id: string): Promise<StoredPlaylist | undefined>;
|
|
35
35
|
setPlaylist(record: StoredPlaylist): Promise<void>;
|
|
36
36
|
isPlaylistNameTaken(name: string, excludeId?: string): Promise<boolean>;
|
|
37
|
+
getUploadedAsset(id: string): Promise<UploadedAssetRecord | undefined>;
|
|
38
|
+
getUploadedAssetBySha256(sha256: string, storageProvider?: UploadedAssetStorageProvider): Promise<UploadedAssetRecord | undefined>;
|
|
39
|
+
setUploadedAsset(record: UploadedAssetRecord): Promise<void>;
|
|
40
|
+
deleteUploadedAsset(id: string): Promise<void>;
|
|
41
|
+
listUploadedAssets(): Promise<UploadedAssetRecord[]>;
|
|
37
42
|
getDeviceAssignments(deviceId: string): Promise<DevicePlaylistAssignment[]>;
|
|
38
43
|
setDeviceAssignments(deviceId: string, assignments: DevicePlaylistAssignment[]): Promise<void>;
|
|
39
44
|
private mapDeviceRegistryRow;
|
|
40
45
|
private mapPairedDeviceRow;
|
|
41
46
|
private mapPlaylistRow;
|
|
47
|
+
private mapUploadedAssetRow;
|
|
42
48
|
}
|
|
43
49
|
//# sourceMappingURL=postgres-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../src/store/postgres-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,IAAI,CAAC;AAE3C,OAAO,KAAK,EACV,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EAGjB,cAAc,EACd,yBAAyB,
|
|
1
|
+
{"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../src/store/postgres-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -92,6 +92,24 @@ export class PostgresStore {
|
|
|
92
92
|
CREATE INDEX IF NOT EXISTS device_assignments_device_order_idx
|
|
93
93
|
ON device_assignments (device_id, sort_order);
|
|
94
94
|
|
|
95
|
+
CREATE TABLE IF NOT EXISTS uploaded_assets (
|
|
96
|
+
id TEXT PRIMARY KEY,
|
|
97
|
+
sha256 TEXT NOT NULL,
|
|
98
|
+
storage_provider TEXT NOT NULL,
|
|
99
|
+
storage_key TEXT NOT NULL,
|
|
100
|
+
url TEXT NOT NULL,
|
|
101
|
+
original_filename TEXT,
|
|
102
|
+
mime_type TEXT,
|
|
103
|
+
resource_type TEXT,
|
|
104
|
+
bytes BIGINT,
|
|
105
|
+
created_at TEXT NOT NULL,
|
|
106
|
+
updated_at TEXT NOT NULL,
|
|
107
|
+
UNIQUE (storage_provider, sha256)
|
|
108
|
+
);
|
|
109
|
+
|
|
110
|
+
CREATE INDEX IF NOT EXISTS uploaded_assets_sha256_idx
|
|
111
|
+
ON uploaded_assets (sha256);
|
|
112
|
+
|
|
95
113
|
INSERT INTO schema_migrations (id, name)
|
|
96
114
|
VALUES (1, 'initial_tomorrowos_store')
|
|
97
115
|
ON CONFLICT (id) DO NOTHING;
|
|
@@ -340,6 +358,82 @@ export class PostgresStore {
|
|
|
340
358
|
`, [target, excludeId ?? null]);
|
|
341
359
|
return result.rows.length > 0;
|
|
342
360
|
}
|
|
361
|
+
async getUploadedAsset(id) {
|
|
362
|
+
await this.ensureReady();
|
|
363
|
+
const result = await this.pool.query(`
|
|
364
|
+
SELECT *
|
|
365
|
+
FROM uploaded_assets
|
|
366
|
+
WHERE id = $1
|
|
367
|
+
`, [id]);
|
|
368
|
+
const row = result.rows[0];
|
|
369
|
+
return row ? this.mapUploadedAssetRow(row) : undefined;
|
|
370
|
+
}
|
|
371
|
+
async getUploadedAssetBySha256(sha256, storageProvider) {
|
|
372
|
+
await this.ensureReady();
|
|
373
|
+
const result = await this.pool.query(`
|
|
374
|
+
SELECT *
|
|
375
|
+
FROM uploaded_assets
|
|
376
|
+
WHERE sha256 = $1
|
|
377
|
+
AND ($2::text IS NULL OR storage_provider = $2)
|
|
378
|
+
LIMIT 1
|
|
379
|
+
`, [sha256, storageProvider ?? null]);
|
|
380
|
+
const row = result.rows[0];
|
|
381
|
+
return row ? this.mapUploadedAssetRow(row) : undefined;
|
|
382
|
+
}
|
|
383
|
+
async setUploadedAsset(record) {
|
|
384
|
+
await this.ensureReady();
|
|
385
|
+
await this.pool.query(`
|
|
386
|
+
INSERT INTO uploaded_assets (
|
|
387
|
+
id,
|
|
388
|
+
sha256,
|
|
389
|
+
storage_provider,
|
|
390
|
+
storage_key,
|
|
391
|
+
url,
|
|
392
|
+
original_filename,
|
|
393
|
+
mime_type,
|
|
394
|
+
resource_type,
|
|
395
|
+
bytes,
|
|
396
|
+
created_at,
|
|
397
|
+
updated_at
|
|
398
|
+
)
|
|
399
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
|
|
400
|
+
ON CONFLICT (id) DO UPDATE SET
|
|
401
|
+
sha256 = EXCLUDED.sha256,
|
|
402
|
+
storage_provider = EXCLUDED.storage_provider,
|
|
403
|
+
storage_key = EXCLUDED.storage_key,
|
|
404
|
+
url = EXCLUDED.url,
|
|
405
|
+
original_filename = EXCLUDED.original_filename,
|
|
406
|
+
mime_type = EXCLUDED.mime_type,
|
|
407
|
+
resource_type = EXCLUDED.resource_type,
|
|
408
|
+
bytes = EXCLUDED.bytes,
|
|
409
|
+
updated_at = EXCLUDED.updated_at
|
|
410
|
+
`, [
|
|
411
|
+
record.id,
|
|
412
|
+
record.sha256,
|
|
413
|
+
record.storageProvider,
|
|
414
|
+
record.storageKey,
|
|
415
|
+
record.url,
|
|
416
|
+
record.originalFilename ?? null,
|
|
417
|
+
record.mimeType ?? null,
|
|
418
|
+
record.resourceType ?? null,
|
|
419
|
+
record.bytes ?? null,
|
|
420
|
+
record.createdAt,
|
|
421
|
+
record.updatedAt
|
|
422
|
+
]);
|
|
423
|
+
}
|
|
424
|
+
async deleteUploadedAsset(id) {
|
|
425
|
+
await this.ensureReady();
|
|
426
|
+
await this.pool.query("DELETE FROM uploaded_assets WHERE id = $1", [id]);
|
|
427
|
+
}
|
|
428
|
+
async listUploadedAssets() {
|
|
429
|
+
await this.ensureReady();
|
|
430
|
+
const result = await this.pool.query(`
|
|
431
|
+
SELECT *
|
|
432
|
+
FROM uploaded_assets
|
|
433
|
+
ORDER BY created_at ASC
|
|
434
|
+
`);
|
|
435
|
+
return result.rows.map((row) => this.mapUploadedAssetRow(row));
|
|
436
|
+
}
|
|
343
437
|
async getDeviceAssignments(deviceId) {
|
|
344
438
|
await this.ensureReady();
|
|
345
439
|
const result = await this.pool.query(`
|
|
@@ -432,4 +526,19 @@ export class PostgresStore {
|
|
|
432
526
|
retiredAt: optionalString(row.retired_at)
|
|
433
527
|
};
|
|
434
528
|
}
|
|
529
|
+
mapUploadedAssetRow(row) {
|
|
530
|
+
return {
|
|
531
|
+
id: row.id,
|
|
532
|
+
sha256: row.sha256,
|
|
533
|
+
storageProvider: row.storage_provider,
|
|
534
|
+
storageKey: row.storage_key,
|
|
535
|
+
url: row.url,
|
|
536
|
+
originalFilename: optionalString(row.original_filename),
|
|
537
|
+
mimeType: optionalString(row.mime_type),
|
|
538
|
+
resourceType: optionalString(row.resource_type),
|
|
539
|
+
bytes: optionalNumber(row.bytes),
|
|
540
|
+
createdAt: row.created_at,
|
|
541
|
+
updatedAt: row.updated_at
|
|
542
|
+
};
|
|
543
|
+
}
|
|
435
544
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { DevicePlaylistAssignment, DeviceRegistryEntry, DeviceRegistryRecord, PairedDeviceEntry, PairedDeviceRecord, PendingCodeEntry, PendingCodeRecord, StoredPlaylist, TomorrowOSMigratableStore } from "./types.js";
|
|
1
|
+
import type { DevicePlaylistAssignment, DeviceRegistryEntry, DeviceRegistryRecord, PairedDeviceEntry, PairedDeviceRecord, PendingCodeEntry, PendingCodeRecord, StoredPlaylist, TomorrowOSMigratableStore, UploadedAssetRecord, UploadedAssetStorageProvider } from "./types.js";
|
|
2
2
|
interface SQLiteStoreOptions {
|
|
3
3
|
databasePath?: string;
|
|
4
4
|
}
|
|
@@ -31,11 +31,17 @@ export declare class SQLiteStore implements TomorrowOSMigratableStore {
|
|
|
31
31
|
getPlaylist(id: string): Promise<StoredPlaylist | undefined>;
|
|
32
32
|
setPlaylist(record: StoredPlaylist): Promise<void>;
|
|
33
33
|
isPlaylistNameTaken(name: string, excludeId?: string): Promise<boolean>;
|
|
34
|
+
getUploadedAsset(id: string): Promise<UploadedAssetRecord | undefined>;
|
|
35
|
+
getUploadedAssetBySha256(sha256: string, storageProvider?: UploadedAssetStorageProvider): Promise<UploadedAssetRecord | undefined>;
|
|
36
|
+
setUploadedAsset(record: UploadedAssetRecord): Promise<void>;
|
|
37
|
+
deleteUploadedAsset(id: string): Promise<void>;
|
|
38
|
+
listUploadedAssets(): Promise<UploadedAssetRecord[]>;
|
|
34
39
|
getDeviceAssignments(deviceId: string): Promise<DevicePlaylistAssignment[]>;
|
|
35
40
|
setDeviceAssignments(deviceId: string, assignments: DevicePlaylistAssignment[]): Promise<void>;
|
|
36
41
|
private mapDeviceRegistryRow;
|
|
37
42
|
private mapPairedDeviceRow;
|
|
38
43
|
private mapPlaylistRow;
|
|
44
|
+
private mapUploadedAssetRow;
|
|
39
45
|
}
|
|
40
46
|
export {};
|
|
41
47
|
//# sourceMappingURL=sqlite-store.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../src/store/sqlite-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EAGjB,cAAc,EACd,yBAAyB,
|
|
1
|
+
{"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../src/store/sqlite-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -98,6 +98,24 @@ export class SQLiteStore {
|
|
|
98
98
|
CREATE INDEX IF NOT EXISTS device_assignments_device_order_idx
|
|
99
99
|
ON device_assignments (device_id, sort_order);
|
|
100
100
|
|
|
101
|
+
CREATE TABLE IF NOT EXISTS uploaded_assets (
|
|
102
|
+
id TEXT PRIMARY KEY,
|
|
103
|
+
sha256 TEXT NOT NULL,
|
|
104
|
+
storage_provider TEXT NOT NULL,
|
|
105
|
+
storage_key TEXT NOT NULL,
|
|
106
|
+
url TEXT NOT NULL,
|
|
107
|
+
original_filename TEXT,
|
|
108
|
+
mime_type TEXT,
|
|
109
|
+
resource_type TEXT,
|
|
110
|
+
bytes INTEGER,
|
|
111
|
+
created_at TEXT NOT NULL,
|
|
112
|
+
updated_at TEXT NOT NULL,
|
|
113
|
+
UNIQUE (storage_provider, sha256)
|
|
114
|
+
);
|
|
115
|
+
|
|
116
|
+
CREATE INDEX IF NOT EXISTS uploaded_assets_sha256_idx
|
|
117
|
+
ON uploaded_assets (sha256);
|
|
118
|
+
|
|
101
119
|
INSERT OR IGNORE INTO schema_migrations (id, name)
|
|
102
120
|
VALUES (1, 'initial_tomorrowos_store');
|
|
103
121
|
`);
|
|
@@ -291,6 +309,63 @@ export class SQLiteStore {
|
|
|
291
309
|
`).get(target, excludeId ?? null, excludeId ?? null);
|
|
292
310
|
return !!row;
|
|
293
311
|
}
|
|
312
|
+
async getUploadedAsset(id) {
|
|
313
|
+
const row = this.db.prepare(`
|
|
314
|
+
SELECT *
|
|
315
|
+
FROM uploaded_assets
|
|
316
|
+
WHERE id = ?
|
|
317
|
+
`).get(id);
|
|
318
|
+
return row ? this.mapUploadedAssetRow(row) : undefined;
|
|
319
|
+
}
|
|
320
|
+
async getUploadedAssetBySha256(sha256, storageProvider) {
|
|
321
|
+
const row = this.db.prepare(`
|
|
322
|
+
SELECT *
|
|
323
|
+
FROM uploaded_assets
|
|
324
|
+
WHERE sha256 = ?
|
|
325
|
+
AND (? IS NULL OR storage_provider = ?)
|
|
326
|
+
LIMIT 1
|
|
327
|
+
`).get(sha256, storageProvider ?? null, storageProvider ?? null);
|
|
328
|
+
return row ? this.mapUploadedAssetRow(row) : undefined;
|
|
329
|
+
}
|
|
330
|
+
async setUploadedAsset(record) {
|
|
331
|
+
this.db.prepare(`
|
|
332
|
+
INSERT INTO uploaded_assets (
|
|
333
|
+
id,
|
|
334
|
+
sha256,
|
|
335
|
+
storage_provider,
|
|
336
|
+
storage_key,
|
|
337
|
+
url,
|
|
338
|
+
original_filename,
|
|
339
|
+
mime_type,
|
|
340
|
+
resource_type,
|
|
341
|
+
bytes,
|
|
342
|
+
created_at,
|
|
343
|
+
updated_at
|
|
344
|
+
)
|
|
345
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
346
|
+
ON CONFLICT(id) DO UPDATE SET
|
|
347
|
+
sha256 = excluded.sha256,
|
|
348
|
+
storage_provider = excluded.storage_provider,
|
|
349
|
+
storage_key = excluded.storage_key,
|
|
350
|
+
url = excluded.url,
|
|
351
|
+
original_filename = excluded.original_filename,
|
|
352
|
+
mime_type = excluded.mime_type,
|
|
353
|
+
resource_type = excluded.resource_type,
|
|
354
|
+
bytes = excluded.bytes,
|
|
355
|
+
updated_at = excluded.updated_at
|
|
356
|
+
`).run(record.id, record.sha256, record.storageProvider, record.storageKey, record.url, record.originalFilename ?? null, record.mimeType ?? null, record.resourceType ?? null, record.bytes ?? null, record.createdAt, record.updatedAt);
|
|
357
|
+
}
|
|
358
|
+
async deleteUploadedAsset(id) {
|
|
359
|
+
this.db.prepare("DELETE FROM uploaded_assets WHERE id = ?").run(id);
|
|
360
|
+
}
|
|
361
|
+
async listUploadedAssets() {
|
|
362
|
+
const rows = this.db.prepare(`
|
|
363
|
+
SELECT *
|
|
364
|
+
FROM uploaded_assets
|
|
365
|
+
ORDER BY created_at ASC
|
|
366
|
+
`).all();
|
|
367
|
+
return rows.map((row) => this.mapUploadedAssetRow(row));
|
|
368
|
+
}
|
|
294
369
|
async getDeviceAssignments(deviceId) {
|
|
295
370
|
const rows = this.db.prepare(`
|
|
296
371
|
SELECT playlist_id, published_version, published_at, snapshot_json
|
|
@@ -364,4 +439,19 @@ export class SQLiteStore {
|
|
|
364
439
|
retiredAt: optionalString(row.retired_at)
|
|
365
440
|
};
|
|
366
441
|
}
|
|
442
|
+
mapUploadedAssetRow(row) {
|
|
443
|
+
return {
|
|
444
|
+
id: row.id,
|
|
445
|
+
sha256: row.sha256,
|
|
446
|
+
storageProvider: row.storage_provider,
|
|
447
|
+
storageKey: row.storage_key,
|
|
448
|
+
url: row.url,
|
|
449
|
+
originalFilename: optionalString(row.original_filename),
|
|
450
|
+
mimeType: optionalString(row.mime_type),
|
|
451
|
+
resourceType: optionalString(row.resource_type),
|
|
452
|
+
bytes: optionalNumber(row.bytes),
|
|
453
|
+
createdAt: row.created_at,
|
|
454
|
+
updatedAt: row.updated_at
|
|
455
|
+
};
|
|
456
|
+
}
|
|
367
457
|
}
|
package/dist/store/types.d.ts
CHANGED
|
@@ -39,9 +39,24 @@ export interface DeviceRegistryEntry {
|
|
|
39
39
|
}
|
|
40
40
|
export interface PlaylistItemRecord {
|
|
41
41
|
url: string;
|
|
42
|
+
assetId?: string;
|
|
42
43
|
type?: string;
|
|
43
44
|
durationMs?: number;
|
|
44
45
|
}
|
|
46
|
+
export type UploadedAssetStorageProvider = "local" | "cloudinary";
|
|
47
|
+
export interface UploadedAssetRecord {
|
|
48
|
+
id: string;
|
|
49
|
+
sha256: string;
|
|
50
|
+
storageProvider: UploadedAssetStorageProvider;
|
|
51
|
+
storageKey: string;
|
|
52
|
+
url: string;
|
|
53
|
+
originalFilename?: string;
|
|
54
|
+
mimeType?: string;
|
|
55
|
+
resourceType?: string;
|
|
56
|
+
bytes?: number;
|
|
57
|
+
createdAt: string;
|
|
58
|
+
updatedAt: string;
|
|
59
|
+
}
|
|
45
60
|
/** Device-local run window: startDate+start → endDate+end (one continuous period, not daily repeat). */
|
|
46
61
|
export interface PlaylistSchedule {
|
|
47
62
|
startDate?: string;
|
|
@@ -98,6 +113,10 @@ export interface TomorrowOSStore {
|
|
|
98
113
|
getPlaylist(id: string): Promise<StoredPlaylist | undefined>;
|
|
99
114
|
setPlaylist(record: StoredPlaylist): Promise<void>;
|
|
100
115
|
isPlaylistNameTaken(name: string, excludeId?: string): Promise<boolean>;
|
|
116
|
+
getUploadedAsset(id: string): Promise<UploadedAssetRecord | undefined>;
|
|
117
|
+
getUploadedAssetBySha256(sha256: string, storageProvider?: UploadedAssetStorageProvider): Promise<UploadedAssetRecord | undefined>;
|
|
118
|
+
setUploadedAsset(record: UploadedAssetRecord): Promise<void>;
|
|
119
|
+
deleteUploadedAsset(id: string): Promise<void>;
|
|
101
120
|
getDeviceAssignments(deviceId: string): Promise<DevicePlaylistAssignment[]>;
|
|
102
121
|
setDeviceAssignments(deviceId: string, assignments: DevicePlaylistAssignment[]): Promise<void>;
|
|
103
122
|
}
|
|
@@ -106,6 +125,7 @@ export interface TomorrowOSDataSnapshot {
|
|
|
106
125
|
deviceRegistry: DeviceRegistryEntry[];
|
|
107
126
|
pairedDevices: PairedDeviceEntry[];
|
|
108
127
|
playlists: StoredPlaylist[];
|
|
128
|
+
uploadedAssets: UploadedAssetRecord[];
|
|
109
129
|
deviceAssignments: Array<{
|
|
110
130
|
deviceId: string;
|
|
111
131
|
assignments: DevicePlaylistAssignment[];
|
|
@@ -114,5 +134,6 @@ export interface TomorrowOSDataSnapshot {
|
|
|
114
134
|
export interface TomorrowOSMigratableStore extends TomorrowOSStore {
|
|
115
135
|
listPendingCodes(): Promise<PendingCodeEntry[]>;
|
|
116
136
|
listDeviceRegistry(): Promise<DeviceRegistryEntry[]>;
|
|
137
|
+
listUploadedAssets(): Promise<UploadedAssetRecord[]>;
|
|
117
138
|
}
|
|
118
139
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"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"}
|
package/dist/tomorrowos.d.ts
CHANGED
|
@@ -123,6 +123,11 @@ export declare class TomorrowOS extends EventEmitter {
|
|
|
123
123
|
private sendCommandToSocket;
|
|
124
124
|
listen(options: ListenOptions): http.Server;
|
|
125
125
|
private handleMediaUpload;
|
|
126
|
+
private storeUploadedMediaAsset;
|
|
127
|
+
private getAssetIdsFromItems;
|
|
128
|
+
private isUploadedAssetReferenced;
|
|
129
|
+
private releaseRemovedPlaylistAssets;
|
|
130
|
+
private deleteUploadedAssetIfUnreferenced;
|
|
126
131
|
private refreshResolvedBrandLogo;
|
|
127
132
|
private tryServeStatic;
|
|
128
133
|
private handleHttp;
|
package/dist/tomorrowos.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,IAAI,MAAM,MAAM,CAAC;AAUxB,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,IAAI,MAAM,MAAM,CAAC;AAUxB,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,EAKV,eAAe,EAGhB,MAAM,kBAAkB,CAAC;AAc1B,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,eAAe,CAAC;IACvB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAsDD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;IACjC,kBAAkB,EAAE,KAAK,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;IACH,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,kBAAkB,EAAE,MAAM,GAAG,IAAI,CAAC;CACnC;AAED,MAAM,WAAW,cAAc;IAC7B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC;IACjC,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAwJD,qBAAa,UAAW,SAAQ,YAAY;IAC1C,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAsC;IACxE,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAuC;IAClE,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,eAAe,CAAgB;gBAE3B,OAAO,EAAE,iBAAiB;IAOtC,yFAAyF;IACnF,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QACxD,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAC;KACtC,CAAC;YAkBY,iBAAiB;IAY/B,6EAA6E;IACvE,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IAgEhE,6EAA6E;IACvE,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IA4BvF,OAAO;uBACU,MAAM;sBA9FgC,MAAM;;2BA+FxC,MAAM;sBA9BgC,MAAM;sBAAY,OAAO;;MA+BlF;IAEF,kFAAkF;IAC5E,WAAW,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAsD9C,OAAO,CAAC,aAAa;IASrB,OAAO,CAAC,aAAa;IAMrB,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,gBAAgB;YAeV,+BAA+B;YAiC/B,iBAAiB;YASjB,iBAAiB;YA4BjB,kBAAkB;IAUhC,uFAAuF;IACvF,OAAO,CAAC,kBAAkB;YAmBZ,gBAAgB;YAMhB,uBAAuB;IAuCrC,MAAM,CAAC,QAAQ,EAAE,MAAM;oBAGD,CAAC,oBACT,MAAM,WACN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;;IAU5E,OAAO,CAAC,mBAAmB;IA6D3B,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC,MAAM;YAmD7B,iBAAiB;YAiDjB,uBAAuB;IAqFrC,OAAO,CAAC,oBAAoB;YASd,yBAAyB;YAQzB,4BAA4B;YAa5B,iCAAiC;YA4BjC,wBAAwB;YAiBxB,cAAc;YAiDd,UAAU;YAyRV,gBAAgB;IAqG9B,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,gBAAgB;CA4HzB"}
|
package/dist/tomorrowos.js
CHANGED
|
@@ -8,8 +8,9 @@ import { generateRandomPairingCode, isValidPairingCodeFormat, normalizePairingCo
|
|
|
8
8
|
import { PlaylistCatalog } from "./playlist-catalog.js";
|
|
9
9
|
import { MemoryStore } from "./store/memory-store.js";
|
|
10
10
|
import { resolveBrandLogoPath, syncProjectAssetsToStaticRoot } from "./brand-assets.js";
|
|
11
|
+
import { deleteCloudinaryAsset, resolveCloudinaryConfig, uploadBufferToCloudinary } from "./cloudinary-storage.js";
|
|
11
12
|
import { probeVideoDurationMs } from "./media-probe.js";
|
|
12
|
-
import { storeUploadIfNeeded } from "./upload-storage.js";
|
|
13
|
+
import { contentHashHex, storeUploadIfNeeded } from "./upload-storage.js";
|
|
13
14
|
function normalizeDevicePlatform(platform, system) {
|
|
14
15
|
const explicit = typeof platform === "string" ? platform.trim().toLowerCase() : "";
|
|
15
16
|
if (explicit)
|
|
@@ -26,13 +27,13 @@ function formatDeviceSystemForFirmware(platform, firmware, existingSystem) {
|
|
|
26
27
|
if (!fw)
|
|
27
28
|
return existingSystem ?? undefined;
|
|
28
29
|
if (platform === "brightsign")
|
|
29
|
-
return
|
|
30
|
+
return "BrightSignOS";
|
|
30
31
|
if (platform === "tizen")
|
|
31
|
-
return
|
|
32
|
+
return "Tizen";
|
|
32
33
|
const base = typeof existingSystem === "string"
|
|
33
34
|
? existingSystem.replace(/\s*\([^)]*\)\s*$/, "").trim()
|
|
34
35
|
: "";
|
|
35
|
-
return base ? `${base}
|
|
36
|
+
return base ? `${base}` : fw;
|
|
36
37
|
}
|
|
37
38
|
function formatDurationMs(ms) {
|
|
38
39
|
if (!Number.isFinite(ms) || ms < 0)
|
|
@@ -139,6 +140,21 @@ function sanitizeUploadFilename(name) {
|
|
|
139
140
|
const base = path.basename(String(name || "upload")).replace(/[^\w.\-()+ ]/g, "_");
|
|
140
141
|
return base.slice(0, 180) || "upload";
|
|
141
142
|
}
|
|
143
|
+
function inferResourceType(mimeType, filename) {
|
|
144
|
+
const mime = String(mimeType || "").toLowerCase();
|
|
145
|
+
if (mime.startsWith("image/"))
|
|
146
|
+
return "image";
|
|
147
|
+
if (mime.startsWith("video/"))
|
|
148
|
+
return "video";
|
|
149
|
+
const ext = path.extname(filename).toLowerCase();
|
|
150
|
+
if ([".jpg", ".jpeg", ".png", ".gif", ".webp", ".svg"].includes(ext)) {
|
|
151
|
+
return "image";
|
|
152
|
+
}
|
|
153
|
+
if ([".mp4", ".mov", ".m4v", ".webm", ".avi", ".mkv"].includes(ext)) {
|
|
154
|
+
return "video";
|
|
155
|
+
}
|
|
156
|
+
return "raw";
|
|
157
|
+
}
|
|
142
158
|
function sendJson(res, status, body) {
|
|
143
159
|
res.writeHead(status, { "Content-Type": "application/json" });
|
|
144
160
|
res.end(JSON.stringify(body));
|
|
@@ -589,16 +605,19 @@ export class TomorrowOS extends EventEmitter {
|
|
|
589
605
|
}
|
|
590
606
|
const rawName = url.searchParams.get("filename") || "upload";
|
|
591
607
|
const safeName = sanitizeUploadFilename(rawName);
|
|
592
|
-
const
|
|
593
|
-
|
|
594
|
-
|
|
608
|
+
const mimeType = typeof req.headers["content-type"] === "string"
|
|
609
|
+
? req.headers["content-type"]
|
|
610
|
+
: undefined;
|
|
611
|
+
const { asset, deduplicated } = await this.storeUploadedMediaAsset(body, safeName, mimeType);
|
|
595
612
|
const durationMs = probeVideoDurationMs(body, safeName);
|
|
596
613
|
const payload = {
|
|
597
614
|
status: "success",
|
|
598
|
-
url:
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
615
|
+
url: asset.url,
|
|
616
|
+
assetId: asset.id,
|
|
617
|
+
filename: asset.storageProvider === "local" ? asset.storageKey : safeName,
|
|
618
|
+
size: asset.bytes ?? body.length,
|
|
619
|
+
contentHash: asset.sha256,
|
|
620
|
+
storageProvider: asset.storageProvider,
|
|
602
621
|
deduplicated
|
|
603
622
|
};
|
|
604
623
|
if (durationMs != null)
|
|
@@ -610,6 +629,117 @@ export class TomorrowOS extends EventEmitter {
|
|
|
610
629
|
sendJson(res, 400, { status: "failed", error: msg });
|
|
611
630
|
}
|
|
612
631
|
}
|
|
632
|
+
async storeUploadedMediaAsset(body, safeName, mimeType) {
|
|
633
|
+
const sha256 = contentHashHex(body);
|
|
634
|
+
const cloudinaryConfig = resolveCloudinaryConfig();
|
|
635
|
+
const storageProvider = cloudinaryConfig
|
|
636
|
+
? "cloudinary"
|
|
637
|
+
: "local";
|
|
638
|
+
const existing = await this.store.getUploadedAssetBySha256(sha256, storageProvider);
|
|
639
|
+
if (existing) {
|
|
640
|
+
if (existing.storageProvider === "cloudinary") {
|
|
641
|
+
return { asset: existing, deduplicated: true };
|
|
642
|
+
}
|
|
643
|
+
const existingLocalPath = path.join(path.resolve(this.staticRoot || "."), "uploads", existing.storageKey);
|
|
644
|
+
try {
|
|
645
|
+
const stat = await fs.stat(existingLocalPath);
|
|
646
|
+
if (stat.size === body.length) {
|
|
647
|
+
return { asset: existing, deduplicated: true };
|
|
648
|
+
}
|
|
649
|
+
}
|
|
650
|
+
catch {
|
|
651
|
+
// Local file is missing; rewrite it below and update this asset record.
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
const now = new Date().toISOString();
|
|
655
|
+
const resourceType = inferResourceType(mimeType, safeName);
|
|
656
|
+
if (cloudinaryConfig) {
|
|
657
|
+
const uploaded = await uploadBufferToCloudinary(body, {
|
|
658
|
+
config: cloudinaryConfig,
|
|
659
|
+
publicId: sha256,
|
|
660
|
+
filename: safeName
|
|
661
|
+
});
|
|
662
|
+
const asset = {
|
|
663
|
+
id: randomUUID(),
|
|
664
|
+
sha256,
|
|
665
|
+
storageProvider: "cloudinary",
|
|
666
|
+
storageKey: uploaded.publicId,
|
|
667
|
+
url: uploaded.secureUrl,
|
|
668
|
+
originalFilename: safeName,
|
|
669
|
+
mimeType,
|
|
670
|
+
resourceType: uploaded.resourceType || resourceType,
|
|
671
|
+
bytes: uploaded.bytes ?? body.length,
|
|
672
|
+
createdAt: now,
|
|
673
|
+
updatedAt: now
|
|
674
|
+
};
|
|
675
|
+
await this.store.setUploadedAsset(asset);
|
|
676
|
+
return { asset, deduplicated: false };
|
|
677
|
+
}
|
|
678
|
+
const uploadsDir = path.join(path.resolve(this.staticRoot || "."), "uploads");
|
|
679
|
+
await fs.mkdir(uploadsDir, { recursive: true });
|
|
680
|
+
const { storedName, deduplicated } = await storeUploadIfNeeded(uploadsDir, body, safeName);
|
|
681
|
+
const asset = {
|
|
682
|
+
id: existing?.id ?? randomUUID(),
|
|
683
|
+
sha256,
|
|
684
|
+
storageProvider: "local",
|
|
685
|
+
storageKey: storedName,
|
|
686
|
+
url: `/uploads/${storedName}`,
|
|
687
|
+
originalFilename: safeName,
|
|
688
|
+
mimeType,
|
|
689
|
+
resourceType,
|
|
690
|
+
bytes: body.length,
|
|
691
|
+
createdAt: existing?.createdAt ?? now,
|
|
692
|
+
updatedAt: now
|
|
693
|
+
};
|
|
694
|
+
await this.store.setUploadedAsset(asset);
|
|
695
|
+
return { asset, deduplicated };
|
|
696
|
+
}
|
|
697
|
+
getAssetIdsFromItems(items) {
|
|
698
|
+
const ids = new Set();
|
|
699
|
+
for (const item of items ?? []) {
|
|
700
|
+
const assetId = String(item.assetId || "").trim();
|
|
701
|
+
if (assetId)
|
|
702
|
+
ids.add(assetId);
|
|
703
|
+
}
|
|
704
|
+
return ids;
|
|
705
|
+
}
|
|
706
|
+
async isUploadedAssetReferenced(assetId) {
|
|
707
|
+
const playlists = await this.store.listPlaylists();
|
|
708
|
+
return playlists.some((playlist) => {
|
|
709
|
+
if (playlist.retired)
|
|
710
|
+
return false;
|
|
711
|
+
return playlist.items.some((item) => item.assetId === assetId);
|
|
712
|
+
});
|
|
713
|
+
}
|
|
714
|
+
async releaseRemovedPlaylistAssets(previousItems, nextItems) {
|
|
715
|
+
const previous = this.getAssetIdsFromItems(previousItems);
|
|
716
|
+
const next = this.getAssetIdsFromItems(nextItems);
|
|
717
|
+
const removed = [...previous].filter((assetId) => !next.has(assetId));
|
|
718
|
+
for (const assetId of removed) {
|
|
719
|
+
await this.deleteUploadedAssetIfUnreferenced(assetId);
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
async deleteUploadedAssetIfUnreferenced(assetId) {
|
|
723
|
+
try {
|
|
724
|
+
if (await this.isUploadedAssetReferenced(assetId))
|
|
725
|
+
return;
|
|
726
|
+
const asset = await this.store.getUploadedAsset(assetId);
|
|
727
|
+
if (!asset)
|
|
728
|
+
return;
|
|
729
|
+
if (asset.storageProvider === "cloudinary") {
|
|
730
|
+
const cloudinaryConfig = resolveCloudinaryConfig();
|
|
731
|
+
if (!cloudinaryConfig) {
|
|
732
|
+
console.warn(`[TomorrowOS] Cloudinary config missing; cannot delete asset ${asset.id}.`);
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
await deleteCloudinaryAsset(cloudinaryConfig, asset.storageKey, asset.resourceType || "image");
|
|
736
|
+
}
|
|
737
|
+
await this.store.deleteUploadedAsset(asset.id);
|
|
738
|
+
}
|
|
739
|
+
catch (err) {
|
|
740
|
+
console.warn("[TomorrowOS] uploaded asset cleanup failed:", err);
|
|
741
|
+
}
|
|
742
|
+
}
|
|
613
743
|
async refreshResolvedBrandLogo() {
|
|
614
744
|
if (!this.staticRoot)
|
|
615
745
|
return;
|
|
@@ -728,14 +858,22 @@ export class TomorrowOS extends EventEmitter {
|
|
|
728
858
|
if (req.method === "POST" && pathname === "/playlists") {
|
|
729
859
|
const body = (await readJsonBody(req));
|
|
730
860
|
try {
|
|
861
|
+
const playlistId = typeof body.id === "string" ? body.id : undefined;
|
|
862
|
+
const previous = playlistId
|
|
863
|
+
? await this.store.getPlaylist(playlistId)
|
|
864
|
+
: undefined;
|
|
865
|
+
const items = Array.isArray(body.items)
|
|
866
|
+
? body.items
|
|
867
|
+
: [];
|
|
731
868
|
const saved = await this.playlists.savePlaylist({
|
|
732
|
-
id:
|
|
869
|
+
id: playlistId,
|
|
733
870
|
name: String(body.name ?? ""),
|
|
734
871
|
schedule: body.schedule && typeof body.schedule === "object"
|
|
735
872
|
? body.schedule
|
|
736
873
|
: undefined,
|
|
737
|
-
items
|
|
874
|
+
items
|
|
738
875
|
});
|
|
876
|
+
await this.releaseRemovedPlaylistAssets(previous?.items, saved.items);
|
|
739
877
|
const paired = await this.store.listPairedDevices();
|
|
740
878
|
const deployResults = [];
|
|
741
879
|
for (const entry of paired) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomorrowos/sdk",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "TomorrowOS CMS server SDK - WebSocket transport, pairing, device commands, optional static CMS UI. Includes CLI (tomorrowos init / build) and starter templates.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -33,8 +33,9 @@
|
|
|
33
33
|
},
|
|
34
34
|
"dependencies": {
|
|
35
35
|
"better-sqlite3": "^12.11.1",
|
|
36
|
+
"cloudinary": "^2.10.0",
|
|
36
37
|
"pg": "^8.22.0",
|
|
37
|
-
"ws": "^8.
|
|
38
|
+
"ws": "^8.21.0"
|
|
38
39
|
},
|
|
39
40
|
"devDependencies": {
|
|
40
41
|
"@types/better-sqlite3": "^7.6.13",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "my-cms",
|
|
3
|
-
"version": "0.5.
|
|
3
|
+
"version": "0.5.5",
|
|
4
4
|
"description": "CMS server on @tomorrowos/sdk. Add your UI (React, static files, etc.) alongside this server.",
|
|
5
5
|
"private": true,
|
|
6
6
|
"type": "module",
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
"build-player": "tomorrowos build --platform tizen"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"@tomorrowos/sdk": "^0.5.
|
|
13
|
+
"@tomorrowos/sdk": "^0.5.5",
|
|
14
14
|
"dotenv": "^17.2.3"
|
|
15
15
|
},
|
|
16
16
|
"devDependencies": {
|
|
@@ -12,7 +12,7 @@ let selectedPlaylistId = null;
|
|
|
12
12
|
/** True while creating a new playlist locally (assets allowed before Save). */
|
|
13
13
|
let playlistDraftActive = false;
|
|
14
14
|
|
|
15
|
-
/** @type {{ id: string, url: string, name: string, type: string, durationMs: number }[]} */
|
|
15
|
+
/** @type {{ id: string, assetId?: string, url: string, name: string, type: string, durationMs: number }[]} */
|
|
16
16
|
let editorItems = [];
|
|
17
17
|
|
|
18
18
|
/** @type {string|null} */
|
|
@@ -429,6 +429,7 @@ function loadEditorFromSelection() {
|
|
|
429
429
|
loadScheduleIntoForm(pl.schedule);
|
|
430
430
|
editorItems = (pl.items || []).map((item) => ({
|
|
431
431
|
id: crypto.randomUUID(),
|
|
432
|
+
assetId: item.assetId,
|
|
432
433
|
url: item.url,
|
|
433
434
|
name: item.url?.split("/").pop() || "asset",
|
|
434
435
|
type: item.type || "image",
|
|
@@ -539,6 +540,7 @@ async function saveCurrentPlaylist() {
|
|
|
539
540
|
try {
|
|
540
541
|
items = editorItems.map((item) => ({
|
|
541
542
|
url: absoluteMediaUrl(item.url),
|
|
543
|
+
assetId: item.assetId,
|
|
542
544
|
type: item.type,
|
|
543
545
|
durationMs: item.durationMs
|
|
544
546
|
}));
|
|
@@ -949,6 +951,7 @@ async function addAssetFromFile(file, queueIndex, queueTotal) {
|
|
|
949
951
|
const durationMs = await resolveVideoDurationMs(file, type, data);
|
|
950
952
|
editorItems.push({
|
|
951
953
|
id: crypto.randomUUID(),
|
|
954
|
+
assetId: data.assetId,
|
|
952
955
|
url: data.url,
|
|
953
956
|
name: file.name,
|
|
954
957
|
type,
|