Commit 7b190fb9 authored by sliao's avatar sliao

todos

parent 14dd7339
github: jaredhanson
.env
var
# Node.js
node_modules/
npm-debug.log*
# Mac OS X
.DS_Store
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>
\ No newline at end of file
# todos-express-password
This app illustrates how to use [Passport](https://www.passportjs.org/) with
[Express](https://expressjs.com/) to sign users in with a username and password.
Use this example as a starting point for your own web applications.
## Quick Start
To run this app, clone the repository and install dependencies:
```bash
$ git clone https://github.com/passport/todos-express-password.git
$ cd todos-express-password
$ npm install
```
Then start the server.
```bash
$ npm start
```
Navigate to [`http://localhost:3000`](http://localhost:3000).
## Tutorial
Follow along with the step-by-step [Username & Password Tutorial](https://www.passportjs.org/tutorials/password/)
to learn how this app was built.
## Overview
This app illustrates how to build a todo app with sign in functionality using
Express, Passport, and the [`passport-local`](https://www.passportjs.org/packages/passport-local/)
strategy.
This app is a traditional web application, in which application logic and data
persistence resides on the server. HTML pages and forms are rendered by the
server and client-side JavaScript is not utilized (or kept to a minimum).
This app is built using the Express web framework. Data is persisted to a
[SQLite](https://www.sqlite.org/) database. HTML pages are rendered using [EJS](https://ejs.co/)
templates, and are styled using vanilla CSS.
When a user first arrives at this app, they are prompted to sign in. Once
authenticated, a login session is established and maintained between the server
and the user's browser with a cookie.
After signing in, the user can view, create, and edit todo items. Interaction
occurs by clicking links and submitting forms, which trigger HTTP requests.
The browser automatically includes the cookie set during login with each of
these requests.
When the server receives a request, it authenticates the cookie and restores the
login session, thus authenticating the user. It then accesses or stores records
in the database associated with the authenticated user.
## Next Steps
* Extend with credential management.
Study [todos-express-password-credential-management](https://github.com/passport/todos-express-password-credential-management)
to learn how to use the [Credential Managment](https://www.w3.org/TR/credential-management-1/)
API to help the user store and select their password.
* Add social login.
Study [todos-express-google](https://github.com/passport/todos-express-google)
to learn how to let users sign in with their social network account, using
their existing profile and avoiding the need to sign up and repeatedly enter
account details.
* Add passwordless.
Study [todos-express-webauthn](https://github.com/passport/todos-express-webauthn)
to learn how to let users sign in with biometrics or a security key.
## License
[The Unlicense](https://opensource.org/licenses/unlicense)
## Credit
Created by [Jared Hanson](https://www.jaredhanson.me/)
var createError = require('http-errors');
var express = require('express');
var path = require('path');
var cookieParser = require('cookie-parser');
var session = require('express-session');
var csrf = require('csurf');
var passport = require('passport');
var logger = require('morgan');
// pass the session to the connect sqlite3 module
// allowing it to inherit from session.Store
var SQLiteStore = require('connect-sqlite3')(session);
var indexRouter = require('./routes/index');
var authRouter = require('./routes/auth');
var app = express();
// view engine setup
app.set('views', path.join(__dirname, 'views'));
app.set('view engine', 'ejs');
app.locals.pluralize = require('pluralize');
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(cookieParser());
app.use(express.static(path.join(__dirname, 'public')));
app.use(session({
secret: 'keyboard cat',
resave: false, // don't save session if unmodified
saveUninitialized: false, // don't create session until something stored
store: new SQLiteStore({ db: 'sessions.db', dir: './var/db' })
}));
app.use(csrf());
app.use(passport.authenticate('session'));
app.use(function(req, res, next) {
var msgs = req.session.messages || [];
res.locals.messages = msgs;
res.locals.hasMessages = !! msgs.length;
req.session.messages = [];
next();
});
app.use(function(req, res, next) {
res.locals.csrfToken = req.csrfToken();
next();
});
app.use('/', indexRouter);
app.use('/', authRouter);
// catch 404 and forward to error handler
app.use(function(req, res, next) {
next(createError(404));
});
// error handler
app.use(function(err, req, res, next) {
// set locals, only providing error in development
res.locals.message = err.message;
res.locals.error = req.app.get('env') === 'development' ? err : {};
// render the error page
res.status(err.status || 500);
res.render('error');
});
module.exports = app;
#!/usr/bin/env node
/**
* Module dependencies.
*/
var app = require('../app');
var debug = require('debug')('todos:server');
var http = require('http');
/**
* Get port from environment and store in Express.
*/
var port = normalizePort(process.env.PORT || '3000');
app.set('port', port);
/**
* Create HTTP server.
*/
var server = http.createServer(app);
/**
* Listen on provided port, on all network interfaces.
*/
server.listen(port);
server.on('error', onError);
server.on('listening', onListening);
/**
* Normalize a port into a number, string, or false.
*/
function normalizePort(val) {
var port = parseInt(val, 10);
if (isNaN(port)) {
// named pipe
return val;
}
if (port >= 0) {
// port number
return port;
}
return false;
}
/**
* Event listener for HTTP server "error" event.
*/
function onError(error) {
if (error.syscall !== 'listen') {
throw error;
}
var bind = typeof port === 'string'
? 'Pipe ' + port
: 'Port ' + port;
// handle specific listen errors with friendly messages
switch (error.code) {
case 'EACCES':
console.error(bind + ' requires elevated privileges');
process.exit(1);
break;
case 'EADDRINUSE':
console.error(bind + ' is already in use');
process.exit(1);
break;
default:
throw error;
}
}
/**
* Event listener for HTTP server "listening" event.
*/
function onListening() {
var addr = server.address();
var bind = typeof addr === 'string'
? 'pipe ' + addr
: 'port ' + addr.port;
debug('Listening on ' + bind);
}
var sqlite3 = require('sqlite3');
var mkdirp = require('mkdirp');
var crypto = require('crypto');
mkdirp.sync('./var/db');
var db = new sqlite3.Database('./var/db/todos.db');
db.serialize(function() {
// create the database schema for the todos app
db.run("CREATE TABLE IF NOT EXISTS users ( \
id INTEGER PRIMARY KEY, \
username TEXT UNIQUE, \
hashed_password BLOB, \
salt BLOB \
)");
db.run("CREATE TABLE IF NOT EXISTS todos ( \
id INTEGER PRIMARY KEY, \
owner_id INTEGER NOT NULL, \
title TEXT NOT NULL, \
completed INTEGER \
)");
// create an initial user (username: alice, password: letmein)
var salt = crypto.randomBytes(16);
db.run('INSERT OR IGNORE INTO users (username, hashed_password, salt) VALUES (?, ?, ?)', [
'alice',
crypto.pbkdf2Sync('letmein', salt, 310000, 32, 'sha256'),
salt
]);
});
module.exports = db;
This source diff could not be displayed because it is too large. You can view the blob instead.
{
"name": "todos-express-password",
"version": "0.0.0",
"private": true,
"description": "Todo app using Express and Passport for sign in with username and password.",
"keywords": [
"example",
"express",
"passport",
"sqlite"
],
"author": {
"name": "Jared Hanson",
"email": "jaredhanson@gmail.com",
"url": "https://www.jaredhanson.me/"
},
"homepage": "https://github.com/passport/todos-express-password",
"repository": {
"type": "git",
"url": "git://github.com/passport/todos-express-password.git"
},
"bugs": {
"url": "https://github.com/passport/todos-express-password/issues"
},
"funding": {
"type": "github",
"url": "https://github.com/sponsors/jaredhanson"
},
"license": "Unlicense",
"scripts": {
"start": "node ./bin/www"
},
"dependencies": {
"connect-ensure-login": "^0.1.1",
"connect-sqlite3": "^0.9.13",
"cookie-parser": "~1.4.4",
"csurf": "^1.11.0",
"debug": "~2.6.9",
"ejs": "~2.6.1",
"express": "~4.16.1",
"express-session": "^1.17.2",
"http-errors": "~1.6.3",
"mkdirp": "^1.0.4",
"morgan": "~1.9.1",
"passport": "^0.6.0",
"passport-local": "^1.0.0",
"pluralize": "^8.0.0",
"sqlite3": "^5.0.2"
}
}
.nav {
position: absolute;
top: -130px;
right: 0;
}
.nav ul {
margin: 0;
list-style: none;
text-align: center;
}
.nav li {
display: inline-block;
height: 40px;
margin-left: 12px;
font-size: 14px;
font-weight: 400;
line-height: 40px;
}
.nav a {
display: block;
color: inherit;
text-decoration: none;
}
.nav a:hover {
border-bottom: 1px solid #DB7676;
}
.nav button {
height: 40px;
}
.nav button:hover {
border-bottom: 1px solid #DB7676;
cursor: pointer;
}
/* background image by Cole Bemis <https://feathericons.com> */
.nav .user {
padding-left: 20px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-user'%3E%3Cpath d='M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2'%3E%3C/path%3E%3Ccircle cx='12' cy='7' r='4'%3E%3C/circle%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center left;
}
/* background image by Cole Bemis <https://feathericons.com> */
.nav .logout {
padding-left: 20px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='18' height='18' viewBox='0 0 24 24' fill='none' stroke='currentColor' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-log-out'%3E%3Cpath d='M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4'%3E%3C/path%3E%3Cpolyline points='16 17 21 12 16 7'%3E%3C/polyline%3E%3Cline x1='21' y1='12' x2='9' y2='12'%3E%3C/line%3E%3C/svg%3E%0A");
background-repeat: no-repeat;
background-position: center left;
}
hr {
margin: 20px 0;
border: 0;
border-top: 1px dashed #c5c5c5;
border-bottom: 1px dashed #f7f7f7;
}
.learn a {
font-weight: normal;
text-decoration: none;
color: #b83f45;
}
.learn a:hover {
text-decoration: underline;
color: #787e7e;
}
.learn h3,
.learn h4,
.learn h5 {
margin: 10px 0;
font-weight: 500;
line-height: 1.2;
color: #000;
}
.learn h3 {
font-size: 24px;
}
.learn h4 {
font-size: 18px;
}
.learn h5 {
margin-bottom: 0;
font-size: 14px;
}
.learn ul {
padding: 0;
margin: 0 0 30px 25px;
}
.learn li {
line-height: 20px;
}
.learn p {
font-size: 15px;
font-weight: 300;
line-height: 1.3;
margin-top: 0;
margin-bottom: 0;
}
#issue-count {
display: none;
}
.quote {
border: none;
margin: 20px 0 60px 0;
}
.quote p {
font-style: italic;
}
.quote p:before {
content: '“';
font-size: 50px;
opacity: .15;
position: absolute;
top: -20px;
left: 3px;
}
.quote p:after {
content: '”';
font-size: 50px;
opacity: .15;
position: absolute;
bottom: -42px;
right: 3px;
}
.quote footer {
position: absolute;
bottom: -40px;
right: 0;
}
.quote footer img {
border-radius: 3px;
}
.quote footer a {
margin-left: 5px;
vertical-align: middle;
}
.speech-bubble {
position: relative;
padding: 10px;
background: rgba(0, 0, 0, .04);
border-radius: 5px;
}
.speech-bubble:after {
content: '';
position: absolute;
top: 100%;
right: 30px;
border: 13px solid transparent;
border-top-color: rgba(0, 0, 0, .04);
}
.learn-bar > .learn {
position: absolute;
width: 272px;
top: 8px;
left: -300px;
padding: 10px;
border-radius: 5px;
background-color: rgba(255, 255, 255, .6);
transition-property: left;
transition-duration: 500ms;
}
@media (min-width: 899px) {
.learn-bar {
width: auto;
padding-left: 300px;
}
.learn-bar > .learn {
left: 8px;
}
}
.todohome {
margin: 130px 0 40px 0;
position: relative;
}
.todohome h1 {
position: absolute;
top: -140px;
width: 100%;
font-size: 80px;
font-weight: 200;
text-align: center;
color: #b83f45;
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.todohome section {
padding-top: 1px;
text-align: center;
}
.todohome h2 {
padding-bottom: 48px;
font-size: 28px;
font-weight: 300;
}
.todohome .button {
padding: 13px 45px;
font-size: 16px;
font-weight: 500;
color: white;
border-radius: 5px;
background: #d83f45;
}
.todohome a.button {
text-decoration: none;
}
html,
body {
margin: 0;
padding: 0;
}
button {
margin: 0;
padding: 0;
border: 0;
background: none;
font-size: 100%;
vertical-align: baseline;
font-family: inherit;
font-weight: inherit;
color: inherit;
-webkit-appearance: none;
appearance: none;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
body {
font: 14px 'Helvetica Neue', Helvetica, Arial, sans-serif;
line-height: 1.4em;
background: #f5f5f5;
color: #111111;
min-width: 230px;
max-width: 550px;
margin: 0 auto;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
font-weight: 300;
}
.hidden {
display: none;
}
.todoapp {
background: #fff;
margin: 130px 0 40px 0;
position: relative;
box-shadow: 0 2px 4px 0 rgba(0, 0, 0, 0.2),
0 25px 50px 0 rgba(0, 0, 0, 0.1);
}
.todoapp input::-webkit-input-placeholder {
font-style: italic;
font-weight: 400;
color: rgba(0, 0, 0, 0.4);
}
.todoapp input::-moz-placeholder {
font-style: italic;
font-weight: 400;
color: rgba(0, 0, 0, 0.4);
}
.todoapp input::input-placeholder {
font-style: italic;
font-weight: 400;
color: rgba(0, 0, 0, 0.4);
}
.todoapp h1 {
position: absolute;
top: -140px;
width: 100%;
font-size: 80px;
font-weight: 200;
text-align: center;
color: #b83f45;
-webkit-text-rendering: optimizeLegibility;
-moz-text-rendering: optimizeLegibility;
text-rendering: optimizeLegibility;
}
.new-todo,
.edit {
position: relative;
margin: 0;
width: 100%;
font-size: 24px;
font-family: inherit;
font-weight: inherit;
line-height: 1.4em;
color: inherit;
padding: 6px;
border: 1px solid #999;
box-shadow: inset 0 -1px 5px 0 rgba(0, 0, 0, 0.2);
box-sizing: border-box;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.new-todo {
padding: 16px 16px 16px 60px;
height: 65px;
border: none;
background: rgba(0, 0, 0, 0.003);
box-shadow: inset 0 -2px 1px rgba(0,0,0,0.03);
}
.main {
position: relative;
z-index: 2;
border-top: 1px solid #e6e6e6;
}
.toggle-all {
width: 1px;
height: 1px;
border: none; /* Mobile Safari */
opacity: 0;
position: absolute;
right: 100%;
bottom: 100%;
}
.toggle-all + label {
display: flex;
align-items: center;
justify-content: center;
width: 45px;
height: 65px;
font-size: 0;
position: absolute;
top: -65px;
left: -0;
}
.toggle-all + label:before {
content: '❯';
display: inline-block;
font-size: 22px;
color: #949494;
padding: 10px 27px 10px 27px;
-webkit-transform: rotate(90deg);
transform: rotate(90deg);
}
.toggle-all:checked + label:before {
color: #484848;
}
.todo-list {
margin: 0;
padding: 0;
list-style: none;
}
.todo-list li {
position: relative;
font-size: 24px;
border-bottom: 1px solid #ededed;
}
.todo-list li:last-child {
border-bottom: none;
}
.todo-list li.editing {
border-bottom: none;
padding: 0;
}
.todo-list li.editing .edit {
display: block;
width: calc(100% - 43px);
padding: 12px 16px;
margin: 0 0 0 43px;
}
.todo-list li.editing .view {
display: none;
}
.todo-list li .toggle {
text-align: center;
width: 40px;
/* auto, since non-WebKit browsers doesn't support input styling */
height: auto;
position: absolute;
top: 0;
bottom: 0;
margin: auto 0;
border: none; /* Mobile Safari */
-webkit-appearance: none;
appearance: none;
}
.todo-list li .toggle {
opacity: 0;
}
.todo-list li .toggle + label {
/*
Firefox requires `#` to be escaped - https://bugzilla.mozilla.org/show_bug.cgi?id=922433
IE and Edge requires *everything* to be escaped to render, so we do that instead of just the `#` - https://developer.microsoft.com/en-us/microsoft-edge/platform/issues/7157459/
*/
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A//www.w3.org/2000/svg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%23949494%22%20stroke-width%3D%223%22/%3E%3C/svg%3E');
background-repeat: no-repeat;
background-position: center left;
}
.todo-list li .toggle:checked + label {
background-image: url('data:image/svg+xml;utf8,%3Csvg%20xmlns%3D%22http%3A%2F%2Fwww.w3.org%2F2000%2Fsvg%22%20width%3D%2240%22%20height%3D%2240%22%20viewBox%3D%22-10%20-18%20100%20135%22%3E%3Ccircle%20cx%3D%2250%22%20cy%3D%2250%22%20r%3D%2250%22%20fill%3D%22none%22%20stroke%3D%22%2359A193%22%20stroke-width%3D%223%22%2F%3E%3Cpath%20fill%3D%22%233EA390%22%20d%3D%22M72%2025L42%2071%2027%2056l-4%204%2020%2020%2034-52z%22%2F%3E%3C%2Fsvg%3E');
}
.todo-list li label {
word-break: break-all;
padding: 15px 15px 15px 60px;
display: block;
line-height: 1.2;
transition: color 0.4s;
font-weight: 400;
color: #484848;
}
.todo-list li.completed label {
color: #949494;
text-decoration: line-through;
}
.todo-list li .destroy {
display: none;
position: absolute;
top: 0;
right: 10px;
bottom: 0;
width: 40px;
height: 40px;
margin: auto 0;
font-size: 30px;
color: #949494;
transition: color 0.2s ease-out;
}
.todo-list li .destroy:hover,
.todo-list li .destroy:focus {
color: #C18585;
}
.todo-list li .destroy:after {
content: '×';
display: block;
height: 100%;
line-height: 1.1;
}
.todo-list li:hover .destroy {
display: block;
}
.todo-list li .edit {
display: none;
}
.todo-list li.editing:last-child {
margin-bottom: -1px;
}
.footer {
padding: 10px 15px;
height: 20px;
text-align: center;
font-size: 15px;
border-top: 1px solid #e6e6e6;
}
.footer:before {
content: '';
position: absolute;
right: 0;
bottom: 0;
left: 0;
height: 50px;
overflow: hidden;
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.2),
0 8px 0 -3px #f6f6f6,
0 9px 1px -3px rgba(0, 0, 0, 0.2),
0 16px 0 -6px #f6f6f6,
0 17px 2px -6px rgba(0, 0, 0, 0.2);
}
.todo-count {
float: left;
text-align: left;
}
.todo-count strong {
font-weight: 300;
}
.filters {
margin: 0;
padding: 0;
list-style: none;
position: absolute;
right: 0;
left: 0;
}
.filters li {
display: inline;
}
.filters li a {
color: inherit;
margin: 3px;
padding: 3px 7px;
text-decoration: none;
border: 1px solid transparent;
border-radius: 3px;
}
.filters li a:hover {
border-color: #DB7676;
}
.filters li a.selected {
border-color: #CE4646;
}
.clear-completed,
html .clear-completed:active {
float: right;
position: relative;
line-height: 19px;
text-decoration: none;
cursor: pointer;
}
.clear-completed:hover {
text-decoration: underline;
}
.info {
margin: 65px auto 0;
color: #4d4d4d;
font-size: 11px;
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.5);
text-align: center;
}
.info p {
line-height: 1;
}
.info a {
color: inherit;
text-decoration: none;
font-weight: 400;
}
.info a:hover {
text-decoration: underline;
}
/*
Hack to remove background from Mobile Safari.
Can't use it globally since it destroys checkboxes in Firefox
*/
@media screen and (-webkit-min-device-pixel-ratio:0) {
.toggle-all,
.todo-list li .toggle {
background: none;
}
.todo-list li .toggle {
height: 40px;
}
}
@media (max-width: 430px) {
.footer {
height: 50px;
}
.filters {
bottom: 10px;
}
}
:focus,
.toggle:focus + label,
.toggle-all:focus + label {
box-shadow: 0 0 2px 2px #CF7D7D;
outline: 0;
}
.prompt {
max-width: 400px;
margin: 50px auto;
padding: 25px;
background: #fff;
border: 1px solid #e6e6e6;
border-radius: 8px;
}
button {
display: block;
padding: 10px;
width: 100%;
border-radius: 3px;
background: #d83f45;
font-size: 14px;
font-weight: 700;
color: white;
cursor: pointer;
}
a.button {
box-sizing: border-box;
display: block;
padding: 10px;
width: 100%;
border-radius: 3px;
background: #000;
font-size: 14px;
font-weight: 700;
text-align: center;
text-decoration: none;
color: white;
}
a.google {
background: #4787ed;
}
button:hover {
background-color: #c83f45;
}
h1 {
margin: 0 0 20px 0;
padding: 0 0 5px 0;
font-size: 24px;
font-weight: 500;
}
h3 {
margin-top: 0;
font-size: 24px;
font-weight: 300;
text-align: center;
color: #b83f45;
}
form section {
margin: 0 0 20px 0;
position: relative; /* for password toggle positioning */
}
label {
display: block;
margin: 0 0 3px 0;
font-size: 14px;
font-weight: 500;
}
input {
box-sizing: border-box;
width: 100%;
padding: 10px;
font-size: 14px;
border: 1px solid #d9d9d9;
border-radius: 5px;
}
input[type=email]:not(:focus):invalid,
input[type=password]:not(:focus):invalid {
color: red;
outline-color: red;
}
hr {
border-top: 1px solid #d9d9d9;
border-bottom: none;
}
p.help {
text-align: center;
font-weight: 400;
}
/* background image by Cole Bemis <https://feathericons.com> */
.messages p {
font-size: 14px;
font-weight: 400;
line-height: 1.3;
color: #d83f45;
padding-left: 20px;
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='16' height='16' viewBox='0 0 24 24' fill='none' stroke='%23d83f45' stroke-width='2' stroke-linecap='round' stroke-linejoin='round' class='feather feather-alert-circle'%3E%3Ccircle cx='12' cy='12' r='10'%3E%3C/circle%3E%3Cline x1='12' y1='8' x2='12' y2='12'%3E%3C/line%3E%3Cline x1='12' y1='16' x2='12.01' y2='16'%3E%3C/line%3E%3C/svg%3E");
background-repeat: no-repeat;
background-position: center left;
}
var express = require('express');
var passport = require('passport');
var LocalStrategy = require('passport-local');
var crypto = require('crypto');
var db = require('../db');
/* Configure password authentication strategy.
*
* The `LocalStrategy` authenticates users by verifying a username and password.
* The strategy parses the username and password from the request and calls the
* `verify` function.
*
* The `verify` function queries the database for the user record and verifies
* the password by hashing the password supplied by the user and comparing it to
* the hashed password stored in the database. If the comparison succeeds, the
* user is authenticated; otherwise, not.
*/
passport.use(new LocalStrategy(function verify(username, password, cb) {
db.get('SELECT * FROM users WHERE username = ?', [ username ], function(err, row) {
if (err) { return cb(err); }
if (!row) { return cb(null, false, { message: 'Incorrect username or password.' }); }
crypto.pbkdf2(password, row.salt, 310000, 32, 'sha256', function(err, hashedPassword) {
if (err) { return cb(err); }
if (!crypto.timingSafeEqual(row.hashed_password, hashedPassword)) {
return cb(null, false, { message: 'Incorrect username or password.' });
}
return cb(null, row);
});
});
}));
/* Configure session management.
*
* When a login session is established, information about the user will be
* stored in the session. This information is supplied by the `serializeUser`
* function, which is yielding the user ID and username.
*
* As the user interacts with the app, subsequent requests will be authenticated
* by verifying the session. The same user information that was serialized at
* session establishment will be restored when the session is authenticated by
* the `deserializeUser` function.
*
* Since every request to the app needs the user ID and username, in order to
* fetch todo records and render the user element in the navigation bar, that
* information is stored in the session.
*/
passport.serializeUser(function(user, cb) {
process.nextTick(function() {
cb(null, { id: user.id, username: user.username });
});
});
passport.deserializeUser(function(user, cb) {
process.nextTick(function() {
return cb(null, user);
});
});
var router = express.Router();
/* GET /login
*
* This route prompts the user to log in.
*
* The 'login' view renders an HTML form, into which the user enters their
* username and password. When the user submits the form, a request will be
* sent to the `POST /login/password` route.
*/
router.get('/login', function(req, res, next) {
res.render('login');
});
/* POST /login/password
*
* This route authenticates the user by verifying a username and password.
*
* A username and password are submitted to this route via an HTML form, which
* was rendered by the `GET /login` route. The username and password is
* authenticated using the `local` strategy. The strategy will parse the
* username and password from the request and call the `verify` function.
*
* Upon successful authentication, a login session will be established. As the
* user interacts with the app, by clicking links and submitting forms, the
* subsequent requests will be authenticated by verifying the session.
*
* When authentication fails, the user will be re-prompted to login and shown
* a message informing them of what went wrong.
*/
router.post('/login/password', passport.authenticate('local', {
successReturnToOrRedirect: '/',
failureRedirect: '/login',
failureMessage: true
}));
/* POST /logout
*
* This route logs the user out.
*/
router.post('/logout', function(req, res, next) {
req.logout(function(err) {
if (err) { return next(err); }
res.redirect('/');
});
});
/* GET /signup
*
* This route prompts the user to sign up.
*
* The 'signup' view renders an HTML form, into which the user enters their
* desired username and password. When the user submits the form, a request
* will be sent to the `POST /signup` route.
*/
router.get('/signup', function(req, res, next) {
res.render('signup');
});
/* POST /signup
*
* This route creates a new user account.
*
* A desired username and password are submitted to this route via an HTML form,
* which was rendered by the `GET /signup` route. The password is hashed and
* then a new user record is inserted into the database. If the record is
* successfully created, the user is logged in.
*/
router.post('/signup', function(req, res, next) {
var salt = crypto.randomBytes(16);
crypto.pbkdf2(req.body.password, salt, 310000, 32, 'sha256', function(err, hashedPassword) {
if (err) { return next(err); }
db.run('INSERT INTO users (username, hashed_password, salt) VALUES (?, ?, ?)', [
req.body.username,
hashedPassword,
salt
], function(err) {
if (err) { return next(err); }
var user = {
id: this.lastID,
username: req.body.username
};
req.login(user, function(err) {
if (err) { return next(err); }
res.redirect('/');
});
});
});
});
module.exports = router;
var express = require('express');
var ensureLogIn = require('connect-ensure-login').ensureLoggedIn;
var db = require('../db');
var ensureLoggedIn = ensureLogIn();
function fetchTodos(req, res, next) {
db.all('SELECT * FROM todos WHERE owner_id = ?', [
req.user.id
], function(err, rows) {
if (err) { return next(err); }
var todos = rows.map(function(row) {
return {
id: row.id,
title: row.title,
completed: row.completed == 1 ? true : false,
url: '/' + row.id
}
});
res.locals.todos = todos;
res.locals.activeCount = todos.filter(function(todo) { return !todo.completed; }).length;
res.locals.completedCount = todos.length - res.locals.activeCount;
next();
});
}
var router = express.Router();
/* GET home page. */
router.get('/', function(req, res, next) {
if (!req.user) { return res.render('home'); }
next();
}, fetchTodos, function(req, res, next) {
res.locals.filter = null;
res.render('index', { user: req.user });
});
router.get('/active', ensureLoggedIn, fetchTodos, function(req, res, next) {
res.locals.todos = res.locals.todos.filter(function(todo) { return !todo.completed; });
res.locals.filter = 'active';
res.render('index', { user: req.user });
});
router.get('/completed', ensureLoggedIn, fetchTodos, function(req, res, next) {
res.locals.todos = res.locals.todos.filter(function(todo) { return todo.completed; });
res.locals.filter = 'completed';
res.render('index', { user: req.user });
});
router.post('/', ensureLoggedIn, function(req, res, next) {
req.body.title = req.body.title.trim();
next();
}, function(req, res, next) {
if (req.body.title !== '') { return next(); }
return res.redirect('/' + (req.body.filter || ''));
}, function(req, res, next) {
db.run('INSERT INTO todos (owner_id, title, completed) VALUES (?, ?, ?)', [
req.user.id,
req.body.title,
req.body.completed == true ? 1 : null
], function(err) {
if (err) { return next(err); }
return res.redirect('/' + (req.body.filter || ''));
});
});
router.post('/:id(\\d+)', ensureLoggedIn, function(req, res, next) {
req.body.title = req.body.title.trim();
next();
}, function(req, res, next) {
if (req.body.title !== '') { return next(); }
db.run('DELETE FROM todos WHERE id = ? AND owner_id = ?', [
req.params.id,
req.user.id
], function(err) {
if (err) { return next(err); }
return res.redirect('/' + (req.body.filter || ''));
});
}, function(req, res, next) {
db.run('UPDATE todos SET title = ?, completed = ? WHERE id = ? AND owner_id = ?', [
req.body.title,
req.body.completed !== undefined ? 1 : null,
req.params.id,
req.user.id
], function(err) {
if (err) { return next(err); }
return res.redirect('/' + (req.body.filter || ''));
});
});
router.post('/:id(\\d+)/delete', ensureLoggedIn, function(req, res, next) {
db.run('DELETE FROM todos WHERE id = ? AND owner_id = ?', [
req.params.id,
req.user.id
], function(err) {
if (err) { return next(err); }
return res.redirect('/' + (req.body.filter || ''));
});
});
router.post('/toggle-all', ensureLoggedIn, function(req, res, next) {
db.run('UPDATE todos SET completed = ? WHERE owner_id = ?', [
req.body.completed !== undefined ? 1 : null,
req.user.id
], function(err) {
if (err) { return next(err); }
return res.redirect('/' + (req.body.filter || ''));
});
});
router.post('/clear-completed', ensureLoggedIn, function(req, res, next) {
db.run('DELETE FROM todos WHERE owner_id = ? AND completed = ?', [
req.user.id,
1
], function(err) {
if (err) { return next(err); }
return res.redirect('/' + (req.body.filter || ''));
});
});
module.exports = router;
<h1><%= message %></h1>
<h2><%= error.status %></h2>
<pre><%= error.stack %></pre>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Express • TodoMVC</title>
<link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="/css/home.css">
</head>
<body>
<section class="todohome">
<header>
<h1>todos</h1>
</header>
<section>
<h2>todos helps you get things done</h2>
<a class="button" href="/login">Sign in</a>
</section>
</section>
<footer class="info">
<p>Created by <a href="https://www.jaredhanson.me">Jared Hanson</a></p>
<p>Part of <a href="https://todomvc.com">TodoMVC</a></p>
<p>Authentication powered by <a href="https://www.passportjs.org">Passport</a></p>
</footer>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Express • TodoMVC</title>
<link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="/css/app.css">
</head>
<body>
<section class="todoapp">
<nav class="nav">
<ul>
<li class="user"><%= user.username %></li>
<li>
<form action="/logout" method="post">
<button class="logout" type="submit">Sign out</button>
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
</form>
</li>
</ul>
</nav>
<header class="header">
<h1>todos</h1>
<form action="/" method="post">
<input class="new-todo" name="title" placeholder="What needs to be done?" autofocus>
<% if (filter) { %>
<input type="hidden" name="filter" value="<%= filter %>"/>
<% } %>
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
</form>
</header>
<% if (activeCount + completedCount > 0) { %>
<section class="main">
<form action="/toggle-all" method="post">
<input id="toggle-all" class="toggle-all" type="checkbox" name="completed" <%- activeCount == 0 ? 'checked' : '' %> onchange="this.form.submit();">
<label for="toggle-all">Mark all as complete</label>
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
</form>
<ul class="todo-list">
<% todos.forEach(function(todo) { %>
<li <%- todo.completed ? 'class="completed"' : '' %>>
<form action="<%= todo.url %>" method="post">
<div class="view">
<input class="toggle" type="checkbox" name="completed" <%- todo.completed ? 'checked' : '' %> onchange="this.form.submit();">
<label ondblclick="this.closest('li').className = this.closest('li').className + ' editing'; this.closest('li').querySelector('input.edit').focus(); this.closest('li').querySelector('input.edit').value = ''; this.closest('li').querySelector('input.edit').value = '<%= todo.title %>';"><%= todo.title %></label>
<button class="destroy" form="delete-<%= todo.id %>"></button>
</div>
<input class="edit" name="title" value="<%= todo.title %>" onkeyup="if (event.keyCode == 27) { this.setAttribute('data-esc', ''); this.closest('li').className = this.closest('li').className.replace('editing', ''); }" onblur="if (this.getAttribute('data-esc') !== null) { return this.removeAttribute('data-esc'); } this.form.submit();">
<% if (filter) { %>
<input type="hidden" name="filter" value="<%= filter %>"/>
<% } %>
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
</form>
<form id="delete-<%= todo.id %>" action="<%= todo.url %>/delete" method="post">
<% if (filter) { %>
<input type="hidden" name="filter" value="<%= filter %>"/>
<% } %>
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
</form>
</li>
<% }); %>
</ul>
</section>
<% } %>
<% if (activeCount + completedCount > 0) { %>
<footer class="footer">
<span class="todo-count"><strong><%= activeCount %></strong> <%= pluralize('item', activeCount) %> left</span>
<ul class="filters">
<li>
<a <%- !filter ? 'class="selected"' : '' %> href="/">All</a>
</li>
<li>
<a <%- filter == 'active' ? 'class="selected"' : '' %> href="/active">Active</a>
</li>
<li>
<a <%- filter == 'completed' ? 'class="selected"' : '' %> href="/completed">Completed</a>
</li>
</ul>
<% if (completedCount > 0) { %>
<form action="/clear-completed" method="post">
<button class="clear-completed">Clear completed</button>
<% if (filter) { %>
<input type="hidden" name="filter" value="<%= filter %>"/>
<% } %>
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
</form>
<% } %>
</footer>
<% } %>
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p>Created by <a href="https://www.jaredhanson.me">Jared Hanson</a></p>
<p>Part of <a href="https://todomvc.com">TodoMVC</a></p>
<p>Authentication powered by <a href="https://www.passportjs.org">Passport</a></p>
</footer>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Express • TodoMVC</title>
<link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="/css/login.css">
</head>
<body>
<section class="prompt">
<h3>todos</h3>
<h1>Sign in</h1>
<% if (hasMessages) { %>
<section class="messages">
<% messages.forEach(function(message) { %>
<p><%= message %></p>
<% }); %>
</section>
<% } %>
<form action="/login/password" method="post">
<section>
<label for="username">Username</label>
<input id="username" name="username" type="text" autocomplete="username" required autofocus>
</section>
<section>
<label for="current-password">Password</label>
<input id="current-password" name="password" type="password" autocomplete="current-password" required>
</section>
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<button type="submit">Sign in</button>
</form>
<hr>
<p class="help">Don't have an account? <a href="/signup">Sign up</a></p>
</section>
<footer class="info">
<p>Created by <a href="https://www.jaredhanson.me">Jared Hanson</a></p>
<p>Part of <a href="https://todomvc.com">TodoMVC</a></p>
<p>Authentication powered by <a href="https://www.passportjs.org">Passport</a></p>
</footer>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Express • TodoMVC</title>
<link rel="stylesheet" href="/css/base.css">
<link rel="stylesheet" href="/css/index.css">
<link rel="stylesheet" href="/css/login.css">
</head>
<body>
<section class="prompt">
<h3>todos</h3>
<h1>Sign up</h1>
<form action="/signup" method="post">
<section>
<label for="username">Username</label>
<input id="username" name="username" type="text" autocomplete="username" required>
</section>
<section>
<label for="new-password">Password</label>
<input id="new-password" name="password" type="password" autocomplete="new-password" required>
</section>
<input type="hidden" name="_csrf" value="<%= csrfToken %>">
<button type="submit">Sign up</button>
</form>
<hr>
<p class="help">Already have an account? <a href="/login">Sign in</a></p>
</section>
<footer class="info">
<p>Created by <a href="https://www.jaredhanson.me">Jared Hanson</a></p>
<p>Part of <a href="https://todomvc.com">TodoMVC</a></p>
<p>Authentication powered by <a href="https://www.passportjs.org">Passport</a></p>
</footer>
</body>
</html>
process.env.TMPDIR = 'tmp' // to avoid the EXDEV rename error, see http://stackoverflow.com/q/21071303/76173
const express = require('express')
const multipart = require('connect-multiparty')
const multipartMiddleware = multipart()
const uploader = require('./uploader-node.js')('tmp')
const app = express()
const fs = require('fs')
// Configure access control allow origin header stuff
const ACCESS_CONTROLL_ALLOW_ORIGIN = true
// Host
// app.use(express.static(__dirname + '/../docs'))
// Handle uploads through Uploader.js
app.post('/upload', multipartMiddleware, function (req, res) {
uploader.post(req, function (status, filename, original_filename, identifier) {
console.log('POST', status, original_filename, identifier)
if (ACCESS_CONTROLL_ALLOW_ORIGIN) {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'content-type')
}
if (status === 'done') {
const s = fs.createWriteStream('./uploads/' + filename)
s.on('finish', function () {
res.status(200)
})
uploader.write(identifier, s, { end: true })
} else {
res.status(/^(partly_done|done)$/.test(status) ? 200 : 500)
}
const data = {
result: true,
message: ''
}
res.send(data)
})
})
app.options('/upload', function (req, res) {
console.log('OPTIONS')
if (ACCESS_CONTROLL_ALLOW_ORIGIN) {
res.header('Access-Control-Allow-Origin', '*')
res.header('Access-Control-Allow-Headers', 'content-type')
}
res.status(200).send()
})
// Handle status checks on chunks through Uploader.js
app.get('/upload', function (req, res) {
uploader.get(req, function (status, filename, original_filename, identifier) {
console.log('GET', status)
if (ACCESS_CONTROLL_ALLOW_ORIGIN) {
res.header('Access-Control-Allow-Origin', '*')
}
if (status == 'found') {
status = 200
} else {
status = 204
}
res.status(status).send()
})
})
app.get('/download/:identifier', function (req, res) {
uploader.write(req.params.identifier, res)
})
app.listen(3000, function () {
console.log('Server started...')
})
const express = require('express')
const app = express()
const port = 3000
const OSS = require('ali-oss')
const client = new OSS({
// yourregion填写Bucket所在地域。以华东1(杭州)为例,Region填写为oss-cn-hangzhou。
region: 'oss-cn-hangzhou',
// 阿里云账号AccessKey拥有所有API的访问权限,风险很高。强烈建议您创建并使用RAM用户进行API访问或日常运维,请登录RAM控制台创建RAM用户。
accessKeyId: 'LTAI5tHf51yCRXf7UfeaVTEJ',
accessKeySecret: 'mk7Y3cMGSm2kp2MDDR3IPUt41IVwzk'
})
async function listBuckets(next) {
try {
// 列举当前账号所有地域下的存储空间。
const result = await client.listBuckets()
console.log(result)
next(result)
} catch (err) {
console.log(err)
}
}
// listBuckets();
app.get('/', (req, res) => {
// res.send('123')
listBuckets(result => {
res.send(result)
})
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
This diff is collapsed.
{
"name": "server",
"version": "1.0.0",
"main": "app.js",
"scripts": {
"server": "node app.js"
},
"dependencies": {
"ali-oss": "^6.17.1",
"connect-multiparty": "^2.2.0",
"express": "^4.3.1",
"mv": "^2.1.1"
}
}
File added
File added
File added
File added
File added
File added
var fs = require('fs'),
path = require('path'),
util = require('util'),
mv = require('mv'),
Stream = require('stream').Stream
module.exports = function (temporaryFolder) {
var $ = this
$.temporaryFolder = temporaryFolder
$.maxFileSize = null
$.fileParameterName = 'file'
try {
fs.mkdirSync($.temporaryFolder)
} catch (e) {}
function cleanIdentifier(identifier) {
return identifier.replace(/[^0-9A-Za-z_-]/g, '')
}
function getChunkFilename(chunkNumber, identifier) {
// Clean up the identifier
identifier = cleanIdentifier(identifier)
// What would the file name be?
return path.resolve($.temporaryFolder, './uploader-' + identifier + '.' + chunkNumber)
}
function validateRequest(chunkNumber, chunkSize, totalSize, identifier, filename, fileSize) {
// Clean up the identifier
identifier = cleanIdentifier(identifier)
// Check if the request is sane
if (
chunkNumber == 0 ||
chunkSize == 0 ||
totalSize == 0 ||
identifier.length == 0 ||
filename.length == 0
) {
return 'non_uploader_request'
}
var numberOfChunks = Math.max(Math.floor(totalSize / (chunkSize * 1.0)), 1)
if (chunkNumber > numberOfChunks) {
return 'invalid_uploader_request1'
}
// Is the file too big?
if ($.maxFileSize && totalSize > $.maxFileSize) {
return 'invalid_uploader_request2'
}
if (typeof fileSize != 'undefined') {
if (chunkNumber < numberOfChunks && fileSize != chunkSize) {
// The chunk in the POST request isn't the correct size
return 'invalid_uploader_request3'
}
if (
numberOfChunks > 1 &&
chunkNumber == numberOfChunks &&
fileSize != (totalSize % chunkSize) + parseInt(chunkSize)
) {
// The chunks in the POST is the last one, and the fil is not the correct size
return 'invalid_uploader_request4'
}
if (numberOfChunks == 1 && fileSize != totalSize) {
// The file is only a single chunk, and the data size does not fit
return 'invalid_uploader_request5'
}
}
return 'valid'
}
//'found', filename, original_filename, identifier
//'not_found', null, null, null
$.get = function (req, callback) {
var chunkNumber = req.param('chunkNumber', 0)
var chunkSize = req.param('chunkSize', 0)
var totalSize = req.param('totalSize', 0)
var identifier = req.param('identifier', '')
var filename = req.param('filename', '')
if (validateRequest(chunkNumber, chunkSize, totalSize, identifier, filename) == 'valid') {
var chunkFilename = getChunkFilename(chunkNumber, identifier)
fs.exists(chunkFilename, function (exists) {
if (exists) {
callback('found', chunkFilename, filename, identifier)
} else {
callback('not_found', null, null, null)
}
})
} else {
callback('not_found', null, null, null)
}
}
//'partly_done', filename, original_filename, identifier
//'done', filename, original_filename, identifier
//'invalid_uploader_request', null, null, null
//'non_uploader_request', null, null, null
$.post = function (req, callback) {
var fields = req.body
var files = req.files
var chunkNumber = fields['chunkNumber']
var chunkSize = fields['chunkSize']
var totalSize = fields['totalSize']
var identifier = cleanIdentifier(fields['identifier'])
var filename = fields['filename']
if (!files[$.fileParameterName] || !files[$.fileParameterName].size) {
callback('invalid_uploader_request', null, null, null)
return
}
var original_filename = files[$.fileParameterName]['originalFilename']
var validation = validateRequest(
chunkNumber,
chunkSize,
totalSize,
identifier,
filename,
files[$.fileParameterName].size
)
if (validation == 'valid') {
var chunkFilename = getChunkFilename(chunkNumber, identifier)
// Save the chunk (TODO: OVERWRITE)
mv(files[$.fileParameterName].path, chunkFilename, function (err) {
if (err) {
console.error('err:', err)
}
// Do we have all the chunks?
var currentTestChunk = 1
var numberOfChunks = Math.max(Math.floor(totalSize / (chunkSize * 1.0)), 1)
var testChunkExists = function () {
fs.exists(getChunkFilename(currentTestChunk, identifier), function (exists) {
if (exists) {
currentTestChunk++
if (currentTestChunk > numberOfChunks) {
callback('done', filename, original_filename, identifier)
} else {
// Recursion
testChunkExists()
}
} else {
callback('partly_done', filename, original_filename, identifier)
}
})
}
testChunkExists()
})
} else {
callback(validation, filename, original_filename, identifier)
}
}
// Pipe chunks directly in to an existsing WritableStream
// r.write(identifier, response);
// r.write(identifier, response, {end:false});
//
// var stream = fs.createWriteStream(filename);
// r.write(identifier, stream);
// stream.on('data', function(data){...});
// stream.on('finish', function(){...});
$.write = function (identifier, writableStream, options) {
options = options || {}
options.end = typeof options['end'] == 'undefined' ? true : options['end']
// Iterate over each chunk
var pipeChunk = function (number) {
var chunkFilename = getChunkFilename(number, identifier)
fs.exists(chunkFilename, function (exists) {
if (exists) {
// If the chunk with the current number exists,
// then create a ReadStream from the file
// and pipe it to the specified writableStream.
var sourceStream = fs.createReadStream(chunkFilename)
sourceStream.pipe(writableStream, {
end: false
})
sourceStream.on('end', function () {
// When the chunk is fully streamed,
// jump to the next one
pipeChunk(number + 1)
})
} else {
// When all the chunks have been piped, end the stream
if (options.end) writableStream.end()
if (options.onDone) options.onDone()
}
})
}
pipeChunk(1)
}
$.clean = function (identifier, options) {
options = options || {}
// Iterate over each chunk
var pipeChunkRm = function (number) {
var chunkFilename = getChunkFilename(number, identifier)
//console.log('removing pipeChunkRm ', number, 'chunkFilename', chunkFilename);
fs.exists(chunkFilename, function (exists) {
if (exists) {
console.log('exist removing ', chunkFilename)
fs.unlink(chunkFilename, function (err) {
if (err && options.onError) options.onError(err)
})
pipeChunkRm(number + 1)
} else {
if (options.onDone) options.onDone()
}
})
}
pipeChunkRm(1)
}
return $
}
File added
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment