Skip to content

Commit b4dfe18

Browse files
MurderlonJack Langston
andauthored
Add support for termination in FileStore (#200)
Co-authored-by: Jack Langston <[email protected]>
1 parent ce40ac2 commit b4dfe18

File tree

12 files changed

+255
-12
lines changed

12 files changed

+255
-12
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
.idea
2+
13
# Logs
24
logs
35
*.log

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,16 @@ server.on(EVENTS.EVENT_UPLOAD_COMPLETE, (event) => {
173173
}
174174
}
175175
```
176+
177+
- `EVENT_FILE_DELETED`: Fired when a `DELETE` request finishes deleting the file
178+
179+
_Example payload:_
180+
```
181+
{
182+
file_id: '7b26bf4d22cf7198d3b3706bf0379794'
183+
184+
}
185+
```
176186
177187
#### Custom `GET` handlers:
178188
Add custom `GET` handlers to suit your needs, similar to [Express routing](https://expressjs.com/en/guide/routing.html).

lib/Server.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ const HeadHandler = require('./handlers/HeadHandler');
1515
const OptionsHandler = require('./handlers/OptionsHandler');
1616
const PatchHandler = require('./handlers/PatchHandler');
1717
const PostHandler = require('./handlers/PostHandler');
18+
const DeleteHandler = require('./handlers/DeleteHandler');
1819
const RequestValidator = require('./validators/RequestValidator');
1920
const EXPOSED_HEADERS = require('./constants').EXPOSED_HEADERS;
2021
const REQUEST_METHODS = require('./constants').REQUEST_METHODS;
@@ -84,6 +85,7 @@ class TusServer extends EventEmitter {
8485
OPTIONS: new OptionsHandler(store),
8586
PATCH: new PatchHandler(store),
8687
POST: new PostHandler(store),
88+
DELETE: new DeleteHandler(store),
8789
};
8890
}
8991

lib/constants.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,13 @@ const ERRORS = {
6363
const EVENT_ENDPOINT_CREATED = 'EVENT_ENDPOINT_CREATED';
6464
const EVENT_FILE_CREATED = 'EVENT_FILE_CREATED';
6565
const EVENT_UPLOAD_COMPLETE = 'EVENT_UPLOAD_COMPLETE';
66+
const EVENT_FILE_DELETED = 'EVENT_FILE_DELETED';
6667

6768
const EVENTS = {
6869
EVENT_ENDPOINT_CREATED,
6970
EVENT_FILE_CREATED,
7071
EVENT_UPLOAD_COMPLETE,
72+
EVENT_FILE_DELETED,
7173
};
7274

7375
module.exports = {

lib/handlers/DeleteHandler.js

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
'use strict';
2+
3+
const BaseHandler = require('./BaseHandler');
4+
const ERRORS = require('../constants').ERRORS;
5+
6+
class DeleteHandler extends BaseHandler {
7+
/**
8+
* Removes a file in the DataStore.
9+
*
10+
* @param {object} req http.incomingMessage
11+
* @param {object} res http.ServerResponse
12+
* @return {function}
13+
*/
14+
send(req, res) {
15+
const file_id = this.getFileIdFromRequest(req);
16+
if (!file_id) {
17+
console.warn('[DeleteHandler]: not a valid path');
18+
return Promise.resolve(super.send(res, 404, {}, 'Invalid path name\n'));
19+
}
20+
req.file_id = file_id;
21+
return this.store.remove(req)
22+
.then(() => {
23+
return super.send(res, 204, {});
24+
})
25+
.catch((error) => {
26+
const status_code = error.status_code || ERRORS.UNKNOWN_ERROR.status_code;
27+
const body = error.body || `${ERRORS.UNKNOWN_ERROR.body}${error.message || ''}\n`;
28+
return super.send(res, status_code, {}, body);
29+
});
30+
}
31+
}
32+
33+
module.exports = DeleteHandler;

lib/stores/DataStore.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,22 @@ class DataStore extends EventEmitter {
6969
});
7070
}
7171

72+
/**
73+
* Called in DELETE requests. This method just deletes the file from the store.
74+
* http://tus.io/protocols/resumable-upload.html#termination
75+
*
76+
* @param {object} req http.incomingMessage
77+
* @return {Promise}
78+
*/
79+
remove(req) {
80+
return new Promise((resolve, reject) => {
81+
const file_id = req.file_id;
82+
this.emit(EVENTS.EVENT_FILE_DELETED, { file_id });
83+
return resolve();
84+
});
85+
}
86+
87+
7288
/**
7389
* Called in PATCH requests. This method should write data
7490
* to the DataStore file, and possibly implement the

lib/stores/FileStore.js

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,6 @@ const log = debug('tus-node-server:stores:filestore');
2323
class FileStore extends DataStore {
2424
constructor(options) {
2525
super(options);
26-
2726
this.directory = options.directory || options.path.replace(/^\//, '');
2827

2928
this.extensions = ['creation', 'creation-with-upload', 'creation-defer-length'];
@@ -69,7 +68,6 @@ class FileStore extends DataStore {
6968
}
7069

7170
const file = new File(file_id, upload_length, upload_defer_length, upload_metadata);
72-
7371
return fs.open(`${this.directory}/${file.id}`, 'w', (err, fd) => {
7472
if (err) {
7573
log('[FileStore] create: Error', err);
@@ -102,6 +100,27 @@ class FileStore extends DataStore {
102100
return fs.createReadStream(path);
103101
}
104102

103+
/**
104+
* Deletes a file.
105+
*
106+
* @param {object} req http.incomingMessage
107+
* @return {Promise}
108+
*/
109+
remove(req) {
110+
return new Promise((resolve, reject) => {
111+
const file_id = req.file_id;
112+
return fs.unlink(`${this.directory}/${file_id}`, (err, fd) => {
113+
if (err) {
114+
console.warn('[FileStore] delete: Error', err);
115+
return reject(ERRORS.FILE_NOT_FOUND);
116+
}
117+
this.emit(EVENTS.EVENT_FILE_DELETED, { file_id });
118+
this.configstore.delete(file_id);
119+
return resolve();
120+
});
121+
});
122+
}
123+
105124
/**
106125
* Write to the file, starting at the provided offset
107126
*

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
],
3434
"main": "index.js",
3535
"scripts": {
36-
"test": "cross-env NODE_ENV=test mocha --timeout 5000",
36+
"test": "cross-env NODE_ENV=test mocha --exit",
3737
"coverage": "nyc npm test",
3838
"demo": "node demo/server.js",
3939
"gcs_demo": "cross-env DATA_STORE=GCSDataStore node demo/server.js",

test/Test-DataStore.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,12 @@ describe('DataStore', () => {
4747
done();
4848
});
4949

50+
it('must have a remove method', (done) => {
51+
datastore.should.have.property('remove');
52+
datastore.remove();
53+
done();
54+
});
55+
5056
it('must have a write method', (done) => {
5157
datastore.should.have.property('write');
5258
datastore.write.should.be.type('function');

test/Test-DeleteHandler.js

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/* eslint-env node, mocha */
2+
'use strict';
3+
4+
const assert = require('assert');
5+
const http = require('http');
6+
const fs = require('fs');
7+
const FileStore = require('../lib/stores/FileStore');
8+
const DeleteHandler = require('../lib/handlers/DeleteHandler');
9+
10+
describe('DeleteHandler', () => {
11+
let res = null;
12+
const path = '/files';
13+
const pathClean = path.replace(/^\//, '');
14+
const namingFunction = (req) => req.url.replace(/\//g, '-');
15+
const store = new FileStore({ path, namingFunction});
16+
const handler = new DeleteHandler(store);
17+
const filePath = "/1234";
18+
const req = { headers: {}, url: path+filePath};
19+
20+
beforeEach((done) => {
21+
res = new http.ServerResponse({ method: 'DELETE' });
22+
done();
23+
});
24+
25+
describe('send()', () => {
26+
it('must be 404 if no file found', (done) => {
27+
handler.send(req, res)
28+
.then(() => {
29+
assert.equal(res.statusCode, 404);
30+
return done();
31+
})
32+
.catch(done);
33+
});
34+
35+
it('must be 404 if invalid path', (done) => {
36+
let new_req = Object.assign({}, req);
37+
new_req.url = '/test/should/not/work/1234';
38+
handler.send(new_req, res)
39+
.then(() => {
40+
assert.equal(res.statusCode, 404);
41+
return done();
42+
})
43+
.catch(done);
44+
});
45+
46+
it('must acknowledge successful DELETE requests with the 204', (done) => {
47+
fs.closeSync(fs.openSync(pathClean+filePath, 'w'));
48+
handler.send(req, res)
49+
.then(() => {
50+
assert.equal(res.statusCode, 204);
51+
return done();
52+
})
53+
.catch(done);
54+
});
55+
});
56+
});

0 commit comments

Comments
 (0)