Spaces:
Sleeping
Sleeping
Vu Minh Chien
commited on
Commit
Β·
8896972
1
Parent(s):
758d55d
Implement HF Dataset for persistent device storage
Browse files- Add HFDatasetManager for HF dataset operations
- Replace file-based storage with HF dataset
- Add automatic dataset creation and management
- Implement fallback to in-memory storage
- Update all device save/load operations
- Add comprehensive error handling
- Include setup documentation
- HF-DATASET-SETUP.md +169 -0
- hf-dataset.js +177 -0
- package.json +3 -1
- server.js +40 -47
HF-DATASET-SETUP.md
ADDED
@@ -0,0 +1,169 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# π€ Hugging Face Dataset Setup Guide
|
2 |
+
|
3 |
+
## Overview
|
4 |
+
|
5 |
+
This server now uses **Hugging Face Dataset** for persistent storage of device tokens instead of local files. This ensures that device data is preserved across Space restarts and provides better data management.
|
6 |
+
|
7 |
+
## π Quick Setup
|
8 |
+
|
9 |
+
### 1. Generate Hugging Face Token
|
10 |
+
|
11 |
+
1. Go to [Hugging Face Settings](https://huggingface.co/settings/tokens)
|
12 |
+
2. Click "Create new token"
|
13 |
+
3. Name: `houzou-notification-server`
|
14 |
+
4. Type: **Write** (required for dataset operations)
|
15 |
+
5. Copy the token
|
16 |
+
|
17 |
+
### 2. Configure Space Secrets
|
18 |
+
|
19 |
+
1. Go to your Space Settings β **Secrets and variables**
|
20 |
+
2. Add these secrets:
|
21 |
+
|
22 |
+
```
|
23 |
+
HF_TOKEN = hf_your_token_here
|
24 |
+
HF_DATASET_ID = Detomo/houzou-devices (optional, defaults to this)
|
25 |
+
```
|
26 |
+
|
27 |
+
### 3. Required Secrets Summary
|
28 |
+
|
29 |
+
Your Space needs these secrets:
|
30 |
+
|
31 |
+
```
|
32 |
+
FIREBASE_SERVICE_ACCOUNT = {your firebase config JSON}
|
33 |
+
HF_TOKEN = hf_your_write_token_here
|
34 |
+
HF_DATASET_ID = Detomo/houzou-devices (optional)
|
35 |
+
```
|
36 |
+
|
37 |
+
## π Dataset Structure
|
38 |
+
|
39 |
+
The dataset will be automatically created with:
|
40 |
+
|
41 |
+
```
|
42 |
+
datasets/Detomo/houzou-devices/
|
43 |
+
βββ README.md # Dataset documentation
|
44 |
+
βββ devices.json # Device tokens and metadata
|
45 |
+
```
|
46 |
+
|
47 |
+
### devices.json Format:
|
48 |
+
```json
|
49 |
+
[
|
50 |
+
[
|
51 |
+
"device-id-1",
|
52 |
+
{
|
53 |
+
"token": "fcm_token_here",
|
54 |
+
"lastUpdated": "2024-01-01T00:00:00.000Z",
|
55 |
+
"platform": "iOS",
|
56 |
+
"appVersion": "1.0.0",
|
57 |
+
"registered": true
|
58 |
+
}
|
59 |
+
]
|
60 |
+
]
|
61 |
+
```
|
62 |
+
|
63 |
+
## π§ Features
|
64 |
+
|
65 |
+
### β
Automatic Dataset Management
|
66 |
+
- Creates dataset if it doesn't exist
|
67 |
+
- Handles permissions automatically
|
68 |
+
- Provides fallback to in-memory storage
|
69 |
+
|
70 |
+
### β
Persistent Storage
|
71 |
+
- Device tokens survive Space restarts
|
72 |
+
- Automatic backup to HF dataset
|
73 |
+
- Version control for device data
|
74 |
+
|
75 |
+
### β
Error Handling
|
76 |
+
- Graceful fallback if HF API is unavailable
|
77 |
+
- Detailed logging for troubleshooting
|
78 |
+
- Continues operation even if dataset access fails
|
79 |
+
|
80 |
+
## π Verification
|
81 |
+
|
82 |
+
### Check Dataset Status
|
83 |
+
```bash
|
84 |
+
curl https://your-space-url.hf.space/
|
85 |
+
```
|
86 |
+
|
87 |
+
Response includes:
|
88 |
+
```json
|
89 |
+
{
|
90 |
+
"message": "Houzou Medical Notification Server",
|
91 |
+
"status": "running",
|
92 |
+
"hfDataset": {
|
93 |
+
"enabled": true,
|
94 |
+
"datasetId": "Detomo/houzou-devices",
|
95 |
+
"hasToken": true
|
96 |
+
},
|
97 |
+
"deviceCount": 5
|
98 |
+
}
|
99 |
+
```
|
100 |
+
|
101 |
+
### View Dataset
|
102 |
+
1. Go to: https://huggingface.co/datasets/Detomo/houzou-devices
|
103 |
+
2. Check `devices.json` for your device data
|
104 |
+
|
105 |
+
## π Troubleshooting
|
106 |
+
|
107 |
+
### Dataset Not Created
|
108 |
+
- Verify `HF_TOKEN` has **Write** permissions
|
109 |
+
- Check Space logs for error messages
|
110 |
+
- Ensure token is valid and not expired
|
111 |
+
|
112 |
+
### Data Not Saving
|
113 |
+
- Check if HF API is responding
|
114 |
+
- Verify dataset permissions
|
115 |
+
- Server will continue with in-memory storage as fallback
|
116 |
+
|
117 |
+
### Common Errors
|
118 |
+
|
119 |
+
**403 Forbidden**
|
120 |
+
- Token doesn't have write permissions
|
121 |
+
- Token is expired or invalid
|
122 |
+
|
123 |
+
**404 Not Found**
|
124 |
+
- Dataset doesn't exist (will be auto-created)
|
125 |
+
- Wrong dataset ID
|
126 |
+
|
127 |
+
**Rate Limiting**
|
128 |
+
- HF API has rate limits
|
129 |
+
- Server will retry automatically
|
130 |
+
|
131 |
+
## π Migration from File Storage
|
132 |
+
|
133 |
+
If you're migrating from file-based storage:
|
134 |
+
|
135 |
+
1. **Backup existing data**: Download your current `devices.json`
|
136 |
+
2. **Set up HF dataset**: Follow steps above
|
137 |
+
3. **Deploy updated server**: The server will automatically use HF dataset
|
138 |
+
4. **Verify**: Check that devices are loaded correctly
|
139 |
+
|
140 |
+
## π Environment Variables
|
141 |
+
|
142 |
+
```bash
|
143 |
+
# Required
|
144 |
+
HF_TOKEN=hf_your_write_token_here
|
145 |
+
FIREBASE_SERVICE_ACCOUNT={"type":"service_account",...}
|
146 |
+
|
147 |
+
# Optional
|
148 |
+
HF_DATASET_ID=Detomo/houzou-devices
|
149 |
+
PORT=7860
|
150 |
+
NODE_ENV=production
|
151 |
+
```
|
152 |
+
|
153 |
+
## π― Benefits
|
154 |
+
|
155 |
+
- **β
Persistent**: Data survives Space restarts
|
156 |
+
- **β
Reliable**: Automatic fallback mechanisms
|
157 |
+
- **β
Scalable**: No file permission issues
|
158 |
+
- **β
Traceable**: Version history in dataset
|
159 |
+
- **β
Secure**: Tokens stored in HF infrastructure
|
160 |
+
|
161 |
+
## π Links
|
162 |
+
|
163 |
+
- [Hugging Face Hub API](https://huggingface.co/docs/hub/api)
|
164 |
+
- [Dataset Management](https://huggingface.co/docs/datasets/)
|
165 |
+
- [Access Tokens](https://huggingface.co/settings/tokens)
|
166 |
+
|
167 |
+
---
|
168 |
+
|
169 |
+
**Ready to deploy!** Your notification server will now use HF Dataset for persistent storage. π
|
hf-dataset.js
ADDED
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
const { HfApi } = require("@huggingface/hub");
|
2 |
+
const fetch = require('node-fetch');
|
3 |
+
|
4 |
+
class HFDatasetManager {
|
5 |
+
constructor() {
|
6 |
+
this.hfToken = process.env.HF_TOKEN;
|
7 |
+
this.datasetId = process.env.HF_DATASET_ID || "Detomo/houzou-devices";
|
8 |
+
this.fileName = "devices.json";
|
9 |
+
this.hfApi = new HfApi({ accessToken: this.hfToken });
|
10 |
+
this.isEnabled = !!this.hfToken;
|
11 |
+
|
12 |
+
console.log(`π€ HF Dataset Manager initialized`);
|
13 |
+
console.log(` Dataset: ${this.datasetId}`);
|
14 |
+
console.log(` Enabled: ${this.isEnabled}`);
|
15 |
+
}
|
16 |
+
|
17 |
+
async loadDevices() {
|
18 |
+
if (!this.isEnabled) {
|
19 |
+
console.log('β οΈ HF Dataset disabled - no token provided');
|
20 |
+
return new Map();
|
21 |
+
}
|
22 |
+
|
23 |
+
try {
|
24 |
+
console.log('π₯ Loading devices from HF dataset...');
|
25 |
+
|
26 |
+
const fileUrl = `https://huggingface.co/datasets/${this.datasetId}/resolve/main/${this.fileName}`;
|
27 |
+
const response = await fetch(fileUrl, {
|
28 |
+
headers: {
|
29 |
+
'Authorization': `Bearer ${this.hfToken}`
|
30 |
+
}
|
31 |
+
});
|
32 |
+
|
33 |
+
if (response.status === 404) {
|
34 |
+
console.log('π No devices file found in dataset, creating new one');
|
35 |
+
await this.saveDevices(new Map());
|
36 |
+
return new Map();
|
37 |
+
}
|
38 |
+
|
39 |
+
if (!response.ok) {
|
40 |
+
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
41 |
+
}
|
42 |
+
|
43 |
+
const data = await response.json();
|
44 |
+
const deviceMap = new Map(data);
|
45 |
+
|
46 |
+
console.log(`π₯ Loaded ${deviceMap.size} devices from HF dataset`);
|
47 |
+
return deviceMap;
|
48 |
+
|
49 |
+
} catch (error) {
|
50 |
+
console.error('β Error loading devices from HF dataset:', error);
|
51 |
+
console.log('β οΈ Falling back to empty device list');
|
52 |
+
return new Map();
|
53 |
+
}
|
54 |
+
}
|
55 |
+
|
56 |
+
async saveDevices(deviceMap) {
|
57 |
+
if (!this.isEnabled) {
|
58 |
+
console.log('β οΈ HF Dataset disabled - cannot save devices');
|
59 |
+
return false;
|
60 |
+
}
|
61 |
+
|
62 |
+
try {
|
63 |
+
console.log(`πΎ Saving ${deviceMap.size} devices to HF dataset...`);
|
64 |
+
|
65 |
+
const devicesArray = Array.from(deviceMap.entries());
|
66 |
+
const jsonData = JSON.stringify(devicesArray, null, 2);
|
67 |
+
|
68 |
+
// Create a blob from the JSON data
|
69 |
+
const blob = new Blob([jsonData], { type: 'application/json' });
|
70 |
+
|
71 |
+
// Upload to HF dataset
|
72 |
+
await this.hfApi.uploadFile({
|
73 |
+
repo: this.datasetId,
|
74 |
+
file: {
|
75 |
+
path: this.fileName,
|
76 |
+
content: blob
|
77 |
+
},
|
78 |
+
commitMessage: `Update devices data - ${new Date().toISOString()}`,
|
79 |
+
repoType: "dataset"
|
80 |
+
});
|
81 |
+
|
82 |
+
console.log(`β
Successfully saved ${deviceMap.size} devices to HF dataset`);
|
83 |
+
return true;
|
84 |
+
|
85 |
+
} catch (error) {
|
86 |
+
console.error('β Error saving devices to HF dataset:', error);
|
87 |
+
return false;
|
88 |
+
}
|
89 |
+
}
|
90 |
+
|
91 |
+
async createDatasetIfNotExists() {
|
92 |
+
if (!this.isEnabled) {
|
93 |
+
console.log('β οΈ HF Dataset disabled - cannot create dataset');
|
94 |
+
return false;
|
95 |
+
}
|
96 |
+
|
97 |
+
try {
|
98 |
+
console.log('π Checking if dataset exists...');
|
99 |
+
|
100 |
+
// Try to get dataset info
|
101 |
+
const datasetInfo = await this.hfApi.datasetInfo({
|
102 |
+
repo: this.datasetId
|
103 |
+
});
|
104 |
+
|
105 |
+
console.log('β
Dataset already exists');
|
106 |
+
return true;
|
107 |
+
|
108 |
+
} catch (error) {
|
109 |
+
if (error.statusCode === 404) {
|
110 |
+
console.log('π Dataset not found, creating new one...');
|
111 |
+
|
112 |
+
try {
|
113 |
+
// Create the dataset
|
114 |
+
await this.hfApi.createRepo({
|
115 |
+
repo: this.datasetId,
|
116 |
+
type: "dataset",
|
117 |
+
private: false
|
118 |
+
});
|
119 |
+
|
120 |
+
console.log('β
Dataset created successfully');
|
121 |
+
|
122 |
+
// Create initial README
|
123 |
+
const readmeContent = `# Houzou Medical Devices Dataset
|
124 |
+
|
125 |
+
This dataset stores FCM tokens and device information for the Houzou Medical app notification system.
|
126 |
+
|
127 |
+
## Files
|
128 |
+
|
129 |
+
- \`devices.json\`: Contains device tokens and metadata
|
130 |
+
|
131 |
+
## Usage
|
132 |
+
|
133 |
+
This dataset is automatically managed by the Houzou Medical Notification Server.
|
134 |
+
|
135 |
+
Last updated: ${new Date().toISOString()}
|
136 |
+
`;
|
137 |
+
|
138 |
+
await this.hfApi.uploadFile({
|
139 |
+
repo: this.datasetId,
|
140 |
+
file: {
|
141 |
+
path: "README.md",
|
142 |
+
content: new Blob([readmeContent], { type: 'text/markdown' })
|
143 |
+
},
|
144 |
+
commitMessage: "Initial dataset setup",
|
145 |
+
repoType: "dataset"
|
146 |
+
});
|
147 |
+
|
148 |
+
// Create initial empty devices file
|
149 |
+
await this.saveDevices(new Map());
|
150 |
+
|
151 |
+
return true;
|
152 |
+
|
153 |
+
} catch (createError) {
|
154 |
+
console.error('β Error creating dataset:', createError);
|
155 |
+
return false;
|
156 |
+
}
|
157 |
+
} else {
|
158 |
+
console.error('β Error checking dataset:', error);
|
159 |
+
return false;
|
160 |
+
}
|
161 |
+
}
|
162 |
+
}
|
163 |
+
|
164 |
+
isReady() {
|
165 |
+
return this.isEnabled;
|
166 |
+
}
|
167 |
+
|
168 |
+
getStatus() {
|
169 |
+
return {
|
170 |
+
enabled: this.isEnabled,
|
171 |
+
datasetId: this.datasetId,
|
172 |
+
hasToken: !!this.hfToken
|
173 |
+
};
|
174 |
+
}
|
175 |
+
}
|
176 |
+
|
177 |
+
module.exports = HFDatasetManager;
|
package.json
CHANGED
@@ -12,7 +12,9 @@
|
|
12 |
"firebase-admin": "^12.0.0",
|
13 |
"cors": "^2.8.5",
|
14 |
"body-parser": "^1.20.2",
|
15 |
-
"dotenv": "^16.3.1"
|
|
|
|
|
16 |
},
|
17 |
"devDependencies": {
|
18 |
"nodemon": "^3.0.2"
|
|
|
12 |
"firebase-admin": "^12.0.0",
|
13 |
"cors": "^2.8.5",
|
14 |
"body-parser": "^1.20.2",
|
15 |
+
"dotenv": "^16.3.1",
|
16 |
+
"@huggingface/hub": "^0.15.1",
|
17 |
+
"node-fetch": "^3.3.2"
|
18 |
},
|
19 |
"devDependencies": {
|
20 |
"nodemon": "^3.0.2"
|
server.js
CHANGED
@@ -3,6 +3,7 @@ const admin = require('firebase-admin');
|
|
3 |
const cors = require('cors');
|
4 |
const bodyParser = require('body-parser');
|
5 |
const os = require('os');
|
|
|
6 |
require('dotenv').config();
|
7 |
|
8 |
const app = express();
|
@@ -59,60 +60,52 @@ const sampleProducts = [
|
|
59 |
const fs = require('fs');
|
60 |
const path = require('path');
|
61 |
|
62 |
-
//
|
63 |
-
const
|
64 |
|
65 |
-
//
|
66 |
-
let fileOperationsEnabled = true;
|
67 |
-
|
68 |
-
// Load devices from file or create empty storage
|
69 |
let deviceTokens = new Map();
|
70 |
|
71 |
-
function
|
72 |
try {
|
73 |
-
|
74 |
-
|
75 |
-
const devicesArray = JSON.parse(data);
|
76 |
-
deviceTokens = new Map(devicesArray);
|
77 |
-
console.log(`π Loaded ${deviceTokens.size} devices from file`);
|
78 |
-
} else {
|
79 |
-
console.log('π No devices file found, starting fresh');
|
80 |
-
// Try to create an empty file to test write permissions
|
81 |
-
try {
|
82 |
-
fs.writeFileSync(DEVICES_FILE, '[]');
|
83 |
-
console.log('π Created empty devices file');
|
84 |
-
} catch (writeError) {
|
85 |
-
console.warn('β οΈ Cannot create devices file, will use in-memory storage only');
|
86 |
-
fileOperationsEnabled = false;
|
87 |
-
}
|
88 |
-
}
|
89 |
} catch (error) {
|
90 |
-
console.error('β Error loading devices
|
91 |
-
console.log('β οΈ Using in-memory storage only');
|
92 |
-
fileOperationsEnabled = false;
|
93 |
deviceTokens = new Map();
|
94 |
}
|
95 |
}
|
96 |
|
97 |
-
function
|
98 |
-
|
99 |
-
|
100 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
}
|
102 |
-
|
|
|
|
|
|
|
103 |
try {
|
104 |
-
|
105 |
-
|
106 |
-
|
|
|
|
|
|
|
|
|
107 |
} catch (error) {
|
108 |
-
console.error('β Error
|
109 |
-
console.log('β οΈ Disabling file operations, using in-memory storage only');
|
110 |
-
fileOperationsEnabled = false;
|
111 |
}
|
112 |
}
|
113 |
|
114 |
-
//
|
115 |
-
|
116 |
|
117 |
// Routes
|
118 |
|
@@ -122,7 +115,7 @@ app.get('/', (req, res) => {
|
|
122 |
message: 'Houzou Medical Notification Server',
|
123 |
status: 'running',
|
124 |
timestamp: new Date().toISOString(),
|
125 |
-
|
126 |
deviceCount: deviceTokens.size
|
127 |
});
|
128 |
});
|
@@ -433,8 +426,8 @@ app.post('/register-token', async (req, res) => {
|
|
433 |
console.warn(`β οΈ Failed to subscribe device to topic: ${topicError.message}`);
|
434 |
}
|
435 |
|
436 |
-
// Save to
|
437 |
-
|
438 |
|
439 |
res.json({
|
440 |
success: true,
|
@@ -476,8 +469,8 @@ app.post('/unregister-token', async (req, res) => {
|
|
476 |
console.log(`π± Token unregistered for device ${deviceId}`);
|
477 |
}
|
478 |
|
479 |
-
// Save to
|
480 |
-
|
481 |
|
482 |
res.json({
|
483 |
success: true,
|
@@ -531,7 +524,7 @@ app.post('/debug-add-device', (req, res) => {
|
|
531 |
registered: true
|
532 |
});
|
533 |
|
534 |
-
|
535 |
|
536 |
console.log(`π§ Debug: Added device ${deviceId} (${platform})`);
|
537 |
|
@@ -642,7 +635,7 @@ app.post('/send-broadcast-notification', async (req, res) => {
|
|
642 |
}
|
643 |
}
|
644 |
});
|
645 |
-
|
646 |
} else {
|
647 |
console.log(`β οΈ No tokens removed - all failures appear to be temporary`);
|
648 |
}
|
@@ -701,7 +694,7 @@ app.listen(PORT, () => {
|
|
701 |
console.log(` Local: http://localhost:${PORT}`);
|
702 |
console.log(` Network: http://${localIP}:${PORT}`);
|
703 |
console.log(`\nπ§ For iPhone app, use: http://${localIP}:${PORT}`);
|
704 |
-
console.log(
|
705 |
});
|
706 |
|
707 |
module.exports = app;
|
|
|
3 |
const cors = require('cors');
|
4 |
const bodyParser = require('body-parser');
|
5 |
const os = require('os');
|
6 |
+
const HFDatasetManager = require('./hf-dataset');
|
7 |
require('dotenv').config();
|
8 |
|
9 |
const app = express();
|
|
|
60 |
const fs = require('fs');
|
61 |
const path = require('path');
|
62 |
|
63 |
+
// Initialize HF Dataset Manager
|
64 |
+
const hfDataset = new HFDatasetManager();
|
65 |
|
66 |
+
// Device storage
|
|
|
|
|
|
|
67 |
let deviceTokens = new Map();
|
68 |
|
69 |
+
async function loadDevicesFromHF() {
|
70 |
try {
|
71 |
+
deviceTokens = await hfDataset.loadDevices();
|
72 |
+
console.log(`π₯ Loaded ${deviceTokens.size} devices from HF dataset`);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
73 |
} catch (error) {
|
74 |
+
console.error('β Error loading devices from HF dataset:', error);
|
|
|
|
|
75 |
deviceTokens = new Map();
|
76 |
}
|
77 |
}
|
78 |
|
79 |
+
async function saveDevicesToHF() {
|
80 |
+
try {
|
81 |
+
const success = await hfDataset.saveDevices(deviceTokens);
|
82 |
+
if (success) {
|
83 |
+
console.log(`πΎ Saved ${deviceTokens.size} devices to HF dataset`);
|
84 |
+
} else {
|
85 |
+
console.log('β οΈ Failed to save devices to HF dataset');
|
86 |
+
}
|
87 |
+
} catch (error) {
|
88 |
+
console.error('β Error saving devices to HF dataset:', error);
|
89 |
}
|
90 |
+
}
|
91 |
+
|
92 |
+
// Initialize HF dataset and load devices on startup
|
93 |
+
async function initializeServer() {
|
94 |
try {
|
95 |
+
// Create dataset if it doesn't exist
|
96 |
+
await hfDataset.createDatasetIfNotExists();
|
97 |
+
|
98 |
+
// Load devices from HF dataset
|
99 |
+
await loadDevicesFromHF();
|
100 |
+
|
101 |
+
console.log('β
Server initialization completed');
|
102 |
} catch (error) {
|
103 |
+
console.error('β Error during server initialization:', error);
|
|
|
|
|
104 |
}
|
105 |
}
|
106 |
|
107 |
+
// Start initialization (don't await here to avoid blocking)
|
108 |
+
initializeServer();
|
109 |
|
110 |
// Routes
|
111 |
|
|
|
115 |
message: 'Houzou Medical Notification Server',
|
116 |
status: 'running',
|
117 |
timestamp: new Date().toISOString(),
|
118 |
+
hfDataset: hfDataset.getStatus(),
|
119 |
deviceCount: deviceTokens.size
|
120 |
});
|
121 |
});
|
|
|
426 |
console.warn(`β οΈ Failed to subscribe device to topic: ${topicError.message}`);
|
427 |
}
|
428 |
|
429 |
+
// Save to HF dataset
|
430 |
+
saveDevicesToHF();
|
431 |
|
432 |
res.json({
|
433 |
success: true,
|
|
|
469 |
console.log(`π± Token unregistered for device ${deviceId}`);
|
470 |
}
|
471 |
|
472 |
+
// Save to HF dataset
|
473 |
+
saveDevicesToHF();
|
474 |
|
475 |
res.json({
|
476 |
success: true,
|
|
|
524 |
registered: true
|
525 |
});
|
526 |
|
527 |
+
saveDevicesToHF();
|
528 |
|
529 |
console.log(`π§ Debug: Added device ${deviceId} (${platform})`);
|
530 |
|
|
|
635 |
}
|
636 |
}
|
637 |
});
|
638 |
+
saveDevicesToHF();
|
639 |
} else {
|
640 |
console.log(`β οΈ No tokens removed - all failures appear to be temporary`);
|
641 |
}
|
|
|
694 |
console.log(` Local: http://localhost:${PORT}`);
|
695 |
console.log(` Network: http://${localIP}:${PORT}`);
|
696 |
console.log(`\nπ§ For iPhone app, use: http://${localIP}:${PORT}`);
|
697 |
+
console.log(`π€ Devices stored in HF dataset: ${hfDataset.datasetId}`);
|
698 |
});
|
699 |
|
700 |
module.exports = app;
|