Initial import
This commit is contained in:
commit
81032e3b0b
90
.gitignore
vendored
Normal file
90
.gitignore
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
|
||||
# Created by https://www.gitignore.io/api/node
|
||||
# Edit at https://www.gitignore.io/?templates=node
|
||||
|
||||
.idea
|
||||
.idea/*
|
||||
|
||||
### Node ###
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# TypeScript v1 declaration files
|
||||
typings/
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variables file
|
||||
.env
|
||||
.env.test
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
|
||||
# next.js build output
|
||||
.next
|
||||
|
||||
# nuxt.js build output
|
||||
.nuxt
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# End of https://www.gitignore.io/api/node
|
60
README.md
Normal file
60
README.md
Normal file
@ -0,0 +1,60 @@
|
||||
<p align="center"><img height="200" src="https://static.garrettmills.dev/assets/flitter/flitter_f.png"></p>
|
||||
|
||||
# Flitter
|
||||
|
||||
Flitter is a quick & ligthweight web app framework based on Express.
|
||||
|
||||
### What?
|
||||
|
||||
Flitter is an MVC style framework that aims to get you up and running faster by providing a structure and a wrapper for Express.js. Files in predictable directories are parsed into routes, middleware, controllers, models, and views.
|
||||
|
||||
Flitter provides access to the Express app, while making it possible to create an app without needing to piece together the Express framework.
|
||||
|
||||
### Flitter Provides:
|
||||
|
||||
- Express at its core
|
||||
- Segmented routing, middleware, and controllers
|
||||
- Busboy for request body parsing
|
||||
- Favicon support
|
||||
- `./flitter` - CLI tools for Flitter (including an interactive shell)
|
||||
- User auth & sessions (see below)
|
||||
|
||||
### How?
|
||||
|
||||
Getting started with Flitter is easy. To create a new project, simply run the following commands:
|
||||
|
||||
```
|
||||
# Download Flitter:
|
||||
git clone https://git.glmdev.tech/flitter/flitter {project_name}
|
||||
cd {project_name}
|
||||
|
||||
# Install dependencies:
|
||||
yarn install
|
||||
|
||||
# Create default config:
|
||||
cp example.env .env
|
||||
|
||||
# Launch Flitter!
|
||||
node index.js
|
||||
|
||||
# Or use the interactive shell
|
||||
./flitter shell
|
||||
```
|
||||
|
||||
And voilà! You should have a Flitter app up and running on port `8000` by default. You can find a more in-depth getting started guide [in the documentation](https://flitter.garrettmills.dev/tutorial-getting-started.html).
|
||||
|
||||
### Why?
|
||||
|
||||
Flitter's creator is a former Laravel junkie, but loves Node and Express. He got tired of having to hammer out the same 500 lines of code to start every project, but didn't want the bulk and obfuscation of larger frameworks like AdonisJS.
|
||||
|
||||
Flitter is designed to be compartmentalized and easy to understand. Every piece of its core functionality is broken into "units." Each of these units does some task like loading config, parsing middleware, connecting to the database, etc. You can see exactly what units your application is loading by viewing the Units file in `config/Units.flitter.js`. Each of Flitters core units are open to view in the [libflitter](https://www.npmjs.com/package/libflitter) package.
|
||||
|
||||
Of course, this also means that Flitter is extremely easy to extend. If you want to add a custom package, simply require it and add its unit to the Units file!
|
||||
|
||||
### Who?
|
||||
|
||||
Flitter was created by [Garrett Mills](https://glmdev.tech/), and its use is governed by the terms of the MIT License as specified in the LICENSE file.
|
||||
|
||||
Of course, that does mean that Flitter is © 2020 Garrett Mills. ;)
|
||||
|
||||
This command will copy the necessary files to your Flitter install. The files are directly accessible and, therefore, completely customizable.
|
23
Units.flitter.js
Normal file
23
Units.flitter.js
Normal file
@ -0,0 +1,23 @@
|
||||
/*
|
||||
* The Flitter Units File
|
||||
* -------------------------------------------------------------
|
||||
* Flitter uses a unit-chain style initialization system. This means that
|
||||
* individual components of Flitter and its add-ons are specified in order
|
||||
* here. Then, when the app is created, Flitter creates a single functional
|
||||
* chain by passing the next unit to the current unit's loading script. This
|
||||
* launches Flitter with a single function call (FlitterApp.up()) and enables
|
||||
* developers to contextualize Flitter within async or callback functions.
|
||||
*/
|
||||
const FlitterUnits = {
|
||||
'Canon' : require('libflitter/canon/CanonicalAccessUnit'),
|
||||
'Services' : require('libflitter/services/ServicesUnit'),
|
||||
'Config' : require('libflitter/config/ConfigUnit'),
|
||||
'Utility' : require('libflitter/utility/UtilityUnit'),
|
||||
'Database' : require('libflitter/database/DatabaseUnit'),
|
||||
'Models' : require('libflitter/database/DatabaseModelsUnit'),
|
||||
'Upload' : require('flitter-upload/UploadUnit'),
|
||||
'Cli' : require('flitter-cli/CliUnit'),
|
||||
'App' : require('./app/ServerUnit'),
|
||||
}
|
||||
|
||||
module.exports = exports = FlitterUnits
|
147
app/ServerUnit.js
Normal file
147
app/ServerUnit.js
Normal file
@ -0,0 +1,147 @@
|
||||
const WebSocket = require('ws')
|
||||
const http = require('http')
|
||||
const url = require('url')
|
||||
const fs = require('fs')
|
||||
const tmp = require('tmp-promise')
|
||||
const websocketStream = require('websocket-stream')
|
||||
const StreamSkip = require('stream-skip')
|
||||
const StreamTake = require('./ws/StreamTake')
|
||||
const Socket = require('./ws/Socket')
|
||||
const { Unit } = require('libflitter')
|
||||
const { NodeDescriptorType } = require('./enum')
|
||||
|
||||
const { Buffer } = require('buffer')
|
||||
const { Readable } = require('stream')
|
||||
|
||||
class ServerUnit extends Unit {
|
||||
static get services() {
|
||||
return [...super.services, 'configs', 'app', 'models', 'upload']
|
||||
}
|
||||
|
||||
sockets = []
|
||||
|
||||
static name() {
|
||||
return 'server'
|
||||
}
|
||||
|
||||
name() {
|
||||
return 'server'
|
||||
}
|
||||
|
||||
async go(app) {
|
||||
this.server = new WebSocket.Server({
|
||||
port: this.configs.get('server.port')
|
||||
})
|
||||
|
||||
this.server.on('connection', socket => {
|
||||
socket = this.app.di().make(Socket, socket)
|
||||
this.sockets.push(socket)
|
||||
socket.ping()
|
||||
})
|
||||
|
||||
this.stream_server = http.createServer().listen(this.configs.get('server.stream_port'))
|
||||
this.stream_socket = websocketStream.createServer({
|
||||
server: this.stream_server,
|
||||
perMessageDeflate: false,
|
||||
binary: true,
|
||||
}, (stream, request) => {
|
||||
const query = url.parse(request.url, true).query
|
||||
if ( !query.writing_file ) this.on_file_stream(stream, request)
|
||||
else this.on_file_write_stream(stream, request)
|
||||
})
|
||||
|
||||
await new Promise(res => {
|
||||
process.on('SIGINT', res)
|
||||
})
|
||||
}
|
||||
|
||||
async on_file_write_stream(stream, request) {
|
||||
let { socket_uuid, node_uuid, length = 4096, position = 0 } = url.parse(request.url, true).query
|
||||
if ( typeof position === 'string' ) position = parseInt(position)
|
||||
if ( typeof length === 'string' ) length = parseInt(length)
|
||||
const socket = this.sockets.find(x => x.uuid === socket_uuid)
|
||||
|
||||
const Node = this.models.get('fs:Node')
|
||||
const node = await Node.findOne({
|
||||
uuid: node_uuid,
|
||||
deleted: false,
|
||||
descriptor_type: NodeDescriptorType.File,
|
||||
})
|
||||
|
||||
if ( !socket.session.temp_write_files ) socket.session.temp_write_files = {}
|
||||
|
||||
const placeholder = socket.session.temp_write_files?.[node.uuid] || await tmp.file()
|
||||
socket.session.temp_write_files[node.uuid] = placeholder
|
||||
|
||||
console.log('Upload placeholder!', placeholder)
|
||||
|
||||
const old_file = await node.uploaded_file()
|
||||
if ( old_file ) {
|
||||
if ( position === 0 ) {
|
||||
// This is a new write, so delete the old file
|
||||
await old_file.delete()
|
||||
delete node.uploaded_file_id
|
||||
} else {
|
||||
await this.upload.provider().download_file(old_file, placeholder.path)
|
||||
}
|
||||
}
|
||||
|
||||
console.log('write stream', stream)
|
||||
console.log('write data', { placeholder, position, length })
|
||||
|
||||
stream.pipe(fs.createWriteStream(placeholder.path, { start: position }))
|
||||
}
|
||||
|
||||
_bufferStream(stream) {
|
||||
const chunks = []
|
||||
return new Promise((resolve, reject) => {
|
||||
stream.on('data', chunk => {
|
||||
console.log('stream data', chunk)
|
||||
chunks.push(chunk)
|
||||
})
|
||||
stream.on('error', reject)
|
||||
stream.on('end', () => {
|
||||
console.log('stream end!')
|
||||
resolve(Buffer.concat(chunks))
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
async on_file_stream(stream, request) {
|
||||
let { socket_uuid, node_uuid, length = 4096, position = 0 } = url.parse(request.url, true).query
|
||||
if ( typeof position === 'string' ) position = parseInt(position)
|
||||
if ( typeof length === 'string' ) length = parseInt(length)
|
||||
// const socket = this.sockets.find(x => x.uuid === socket_uuid)
|
||||
|
||||
const Node = this.models.get('fs:Node')
|
||||
const node = await Node.findOne({
|
||||
uuid: node_uuid,
|
||||
deleted: false,
|
||||
descriptor_type: NodeDescriptorType.File,
|
||||
})
|
||||
|
||||
const file = await node.uploaded_file()
|
||||
|
||||
if ( file ) {
|
||||
const readable = this.upload.provider().read_stream(file)
|
||||
const slicer = new StreamSkip({ skip: position })
|
||||
const taker = new StreamTake({ take: length })
|
||||
|
||||
readable.pipe(slicer).pipe(taker).pipe(stream)
|
||||
} else {
|
||||
// If no data was written, just return an empty file
|
||||
const empty = new Readable()
|
||||
empty.push('')
|
||||
empty.push(null)
|
||||
empty.pipe(stream)
|
||||
}
|
||||
}
|
||||
|
||||
async cleanup(app) {
|
||||
this.server.close()
|
||||
this.stream_server.close()
|
||||
this.stream_socket.close()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = ServerUnit
|
6
app/enum.js
Normal file
6
app/enum.js
Normal file
@ -0,0 +1,6 @@
|
||||
const NodeDescriptorType = {
|
||||
File: 'file',
|
||||
Directory: 'directory',
|
||||
}
|
||||
|
||||
module.exports = exports = { NodeDescriptorType }
|
18
app/models/Token.model.js
Normal file
18
app/models/Token.model.js
Normal file
@ -0,0 +1,18 @@
|
||||
const { Model } = require('flitter-orm')
|
||||
const uuid = require('uuid').v4
|
||||
|
||||
const gen_token = () => {
|
||||
return `${uuid()}${uuid()}${uuid()}${uuid()}`.replace(/-/g, '')
|
||||
}
|
||||
|
||||
class Token extends Model {
|
||||
static get schema() {
|
||||
return {
|
||||
user_uuid: String,
|
||||
token_value: { type: String, default: gen_token },
|
||||
active: { type: Boolean, default: true },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = Token
|
31
app/models/User.model.js
Normal file
31
app/models/User.model.js
Normal file
@ -0,0 +1,31 @@
|
||||
const { Model } = require('flitter-orm')
|
||||
const uuid = require('uuid').v4
|
||||
|
||||
class User extends Model {
|
||||
static get services() {
|
||||
return [...super.services, 'models']
|
||||
}
|
||||
|
||||
static get schema() {
|
||||
return {
|
||||
uuid: { type: String, default: uuid },
|
||||
username: String,
|
||||
}
|
||||
}
|
||||
|
||||
async get_token() {
|
||||
const Token = this.models.get('Token')
|
||||
const existing = await Token.findOne({
|
||||
active: true,
|
||||
user_uuid: this.uuid,
|
||||
})
|
||||
|
||||
if ( existing ) return existing
|
||||
|
||||
const generated = new Token({ user_uuid: this.uuid })
|
||||
await generated.save()
|
||||
return generated
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = User
|
160
app/models/fs/Node.model.js
Normal file
160
app/models/fs/Node.model.js
Normal file
@ -0,0 +1,160 @@
|
||||
const uuid = require('uuid').v4
|
||||
const { Model } = require('flitter-orm')
|
||||
const { NodeDescriptorType } = require('../../enum')
|
||||
|
||||
class Node extends Model {
|
||||
static get services() {
|
||||
return [...super.services, 'models']
|
||||
}
|
||||
|
||||
static get schema() {
|
||||
return {
|
||||
uuid: { type: String, default: uuid },
|
||||
pied_name: String,
|
||||
pied_parent_path: { type: String, default: '/' },
|
||||
overlay_name: { type: String, default: 'mainline' },
|
||||
mtime: { type: Date, default: () => new Date },
|
||||
atime: { type: Date, default: () => new Date },
|
||||
ctime: { type: Date, default: () => new Date },
|
||||
mode: { type: Number, default: 33188 },
|
||||
size: { type: Number, default: 0 },
|
||||
|
||||
descriptor_type: { type: String, default: NodeDescriptorType.File },
|
||||
uploaded_file_id: String,
|
||||
|
||||
deleted: { type: Boolean, default: false },
|
||||
root: { type: Boolean, default: false },
|
||||
}
|
||||
}
|
||||
|
||||
static path_parts(path) {
|
||||
const path_parts = path.split('/')
|
||||
const pied_name = path_parts.pop()
|
||||
let pied_parent_path = path_parts.join('/')
|
||||
if ( !pied_parent_path.startsWith('/') ) pied_parent_path = `/${pied_parent_path}`
|
||||
|
||||
return [pied_parent_path, pied_name]
|
||||
}
|
||||
|
||||
static async get_root() {
|
||||
let root = await this.findOne({
|
||||
deleted: false, root: true, pied_parent_path: '/', pied_name: '/',
|
||||
})
|
||||
|
||||
if ( !root ) {
|
||||
root = new this({
|
||||
pied_name: '/',
|
||||
mode: 16877,
|
||||
root: true,
|
||||
descriptor_type: NodeDescriptorType.Directory,
|
||||
})
|
||||
|
||||
await root.save()
|
||||
}
|
||||
|
||||
return root
|
||||
}
|
||||
|
||||
static async get_path(path, workspace = 'mainline') {
|
||||
const [pied_parent_path, pied_name] = this.path_parts(path)
|
||||
|
||||
const nodes = await this.find({
|
||||
pied_name,
|
||||
pied_parent_path,
|
||||
overlay_name: {
|
||||
$in: [workspace, 'mainline'],
|
||||
},
|
||||
deleted: false,
|
||||
root: false,
|
||||
})
|
||||
|
||||
if ( nodes.length === 1 ) {
|
||||
return nodes[0]
|
||||
}
|
||||
|
||||
if ( nodes.length === 2 ) {
|
||||
return nodes.find(x => x.overlay_name === workspace)
|
||||
}
|
||||
}
|
||||
|
||||
static async list_path(path, workspace = 'mainline') {
|
||||
const nodes = await this.find({
|
||||
pied_parent_path: path,
|
||||
overlay_name: workspace,
|
||||
// deleted: false,
|
||||
root: false,
|
||||
})
|
||||
|
||||
const mainline_nodes = await this.find({
|
||||
pied_name: {
|
||||
$nin: nodes.map(x => x.pied_name),
|
||||
},
|
||||
pied_parent_path: path,
|
||||
overlay_name: 'mainline',
|
||||
deleted: false,
|
||||
root: false,
|
||||
})
|
||||
|
||||
return [...nodes, ...mainline_nodes].filter(x => !x.deleted)
|
||||
}
|
||||
|
||||
async uploaded_file() {
|
||||
if ( !this.uploaded_file_id ) return;
|
||||
|
||||
const File = this.models.get('upload::File')
|
||||
return File.findById(this.uploaded_file_id)
|
||||
}
|
||||
|
||||
async all_descendants(workspace = 'mainline') {
|
||||
const formatted_parent = `${this.pied_parent_path === '/' ? '/' : this.pied_parent_path + '/'}${this.pied_name}`
|
||||
const nodes = await this.constructor.find({
|
||||
pied_parent_path: {
|
||||
$regex: this.root ? `/.*` : `(?:${formatted_parent}/.*)|(?:${formatted_parent}$)`,
|
||||
},
|
||||
overlay_name: workspace,
|
||||
root: false,
|
||||
// deleted: false,
|
||||
})
|
||||
|
||||
const mainline_nodes = await this.constructor.find({
|
||||
$and: [
|
||||
{
|
||||
pied_parent_path: {
|
||||
$regex: this.root ? `/.*` : `(?:${formatted_parent}/.*)|(?:${formatted_parent}$)`,
|
||||
},
|
||||
overlay_name: 'mainline',
|
||||
root: false,
|
||||
deleted: false,
|
||||
},
|
||||
...nodes.map(node => {
|
||||
return {
|
||||
pied_parent_path: {
|
||||
$ne: node.pied_parent_path,
|
||||
},
|
||||
pied_name: {
|
||||
$ne: node.pied_name,
|
||||
},
|
||||
}
|
||||
})
|
||||
],
|
||||
})
|
||||
|
||||
return [...nodes, ...mainline_nodes].filter(x => !x.deleted)
|
||||
}
|
||||
|
||||
to_api() {
|
||||
return {
|
||||
pied_name: this.pied_name,
|
||||
mtime: this.mtime,
|
||||
atime: this.atime,
|
||||
ctime: this.ctime,
|
||||
mode: this.mode,
|
||||
nlink: 1,
|
||||
uid: 0, // TODO
|
||||
gid: 0, // TODO
|
||||
size: this.size,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = Node
|
4
app/shared.js
Normal file
4
app/shared.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = exports = {
|
||||
Errors: require('../../shared/Errors'),
|
||||
Message: require('../../shared/Message'),
|
||||
}
|
76
app/ws/Socket.js
Normal file
76
app/ws/Socket.js
Normal file
@ -0,0 +1,76 @@
|
||||
const uuid = require('uuid').v4
|
||||
const { Message } = require('../shared')
|
||||
const { Injectable } = require('flitter-di')
|
||||
|
||||
class Socket extends Injectable {
|
||||
static get services() {
|
||||
return [...super.services, 'app', 'output']
|
||||
}
|
||||
|
||||
messages = []
|
||||
|
||||
constructor(socket) {
|
||||
super()
|
||||
this.socket = socket
|
||||
this.uuid = uuid()
|
||||
this.session = {}
|
||||
|
||||
this.socket.on('message', msg => this.on_message(msg))
|
||||
}
|
||||
|
||||
ping() {
|
||||
this.send(Message.route('meta.ping').expect_response())
|
||||
}
|
||||
|
||||
send(message) {
|
||||
this.output.debug(message)
|
||||
|
||||
if ( typeof message === 'string' ) {
|
||||
this.socket.send(message)
|
||||
return
|
||||
}
|
||||
|
||||
if ( message.needs_response ) {
|
||||
this.messages.push(message)
|
||||
}
|
||||
|
||||
const serial = message.serialize()
|
||||
this.socket.send(serial)
|
||||
}
|
||||
|
||||
async on_message(msg) {
|
||||
this.output.info(msg)
|
||||
const message = new Message(msg)
|
||||
const response = new Message()
|
||||
message.socket = this
|
||||
|
||||
if ( message.is_response() ) {
|
||||
// Try to find the message that sent the request
|
||||
const request = this.messages.find(x => x.uuid() === message.response_to())
|
||||
if ( request ) {
|
||||
await request._response_callback(message)
|
||||
request.has_response = true;
|
||||
} else {
|
||||
this.send(
|
||||
response.response_to(message.uuid())
|
||||
.error(Errors.InvalidReplyUUID)
|
||||
)
|
||||
}
|
||||
|
||||
this.messages = this.messages.filter(x => !x.has_response)
|
||||
} else {
|
||||
let handler;
|
||||
try {
|
||||
handler = require(`./routes/${message.route()}`)
|
||||
} catch (e) {}
|
||||
|
||||
if ( !handler ) {
|
||||
return this.send(socket, response.error(Errors.InvalidMessageRoute))
|
||||
}
|
||||
|
||||
await handler(message, this.app.di().container.proxy())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = exports = Socket
|
35
app/ws/StreamTake.js
Normal file
35
app/ws/StreamTake.js
Normal file
@ -0,0 +1,35 @@
|
||||
const stream = require('stream')
|
||||
const util = require('util')
|
||||
|
||||
const Transform = stream.Transform
|
||||
|
||||
function Take(options) {
|
||||
// allow use without new
|
||||
if (!(this instanceof Take)) {
|
||||
return new Take(options);
|
||||
}
|
||||
|
||||
this._toTake = options.take || undefined
|
||||
|
||||
// init Transform
|
||||
Transform.call(this, options);
|
||||
}
|
||||
|
||||
util.inherits(Take, Transform);
|
||||
|
||||
Take.prototype._transform = function (chunk, enc, cb) {
|
||||
if ( typeof this._toTake == 'undefined' ) {
|
||||
this.push(chunk)
|
||||
}
|
||||
else if (this._toTake > chunk.length) {
|
||||
this._toTake -= chunk.length;
|
||||
this.push(chunk)
|
||||
} else {
|
||||
if (this._toTake !== chunk.length) this.push(chunk.slice(0, this._toTake))
|
||||
this._toTake = 0;
|
||||
}
|
||||
|
||||
cb();
|
||||
};
|
||||
|
||||
module.exports = Take;
|
52
app/ws/routes/fs.create.js
Normal file
52
app/ws/routes/fs.create.js
Normal file
@ -0,0 +1,52 @@
|
||||
const { Errors } = require('../../shared')
|
||||
const { NodeDescriptorType } = require('../../enum')
|
||||
|
||||
module.exports = exports = async (message, di) => {
|
||||
const new_fd = (node_uuid) => {
|
||||
if ( !message.socket.session.last_file_descriptor ) {
|
||||
message.socket.session.last_file_descriptor = 0
|
||||
message.socket.session.file_descriptors = {}
|
||||
}
|
||||
|
||||
message.socket.session.last_file_descriptor += 1
|
||||
message.socket.session.file_descriptors[message.socket.session.last_file_descriptor] = node_uuid
|
||||
return message.socket.session.last_file_descriptor
|
||||
}
|
||||
|
||||
const Node = di.models.get('fs:Node')
|
||||
const { path, mode } = message.data()
|
||||
|
||||
if ( !path ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeDoesNotExist)
|
||||
)
|
||||
}
|
||||
|
||||
if ( path === '/' ) {
|
||||
return message.send_response(
|
||||
message.fresh.error(Errors.NodeAlreadyExists)
|
||||
)
|
||||
}
|
||||
|
||||
const existing_node = await Node.get_path(path, message.socket.session.overlay_name || 'mainline')
|
||||
if ( existing_node ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeAlreadyExists)
|
||||
)
|
||||
}
|
||||
|
||||
const [pied_parent_path, pied_name] = Node.path_parts(path)
|
||||
const node = new Node({
|
||||
pied_name,
|
||||
pied_parent_path,
|
||||
overlay_name: message.socket.session.overlay_name || 'mainline',
|
||||
mode: 33188, // TODO account for the mode from the client!
|
||||
descriptor_type: NodeDescriptorType.File,
|
||||
})
|
||||
|
||||
await node.save()
|
||||
|
||||
message.send_response(
|
||||
message.fresh().data({ node: node.to_api(), descriptor: new_fd(node.uuid) })
|
||||
)
|
||||
}
|
26
app/ws/routes/fs.getattr.js
Normal file
26
app/ws/routes/fs.getattr.js
Normal file
@ -0,0 +1,26 @@
|
||||
const { Errors } = require('../../shared')
|
||||
|
||||
module.exports = exports = async (message, di) => {
|
||||
const Node = di.models.get('fs:Node')
|
||||
const { path } = message.data()
|
||||
|
||||
let data
|
||||
if ( path === '/' ) {
|
||||
const root = await Node.get_root()
|
||||
data = root.to_api()
|
||||
} else {
|
||||
const node = await Node.get_path(path, message.socket.session.overlay_name || 'mainline')
|
||||
|
||||
if ( !node ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeDoesNotExist)
|
||||
)
|
||||
}
|
||||
|
||||
data = node.to_api()
|
||||
}
|
||||
|
||||
message.send_response(
|
||||
message.fresh().data({ node: data })
|
||||
)
|
||||
}
|
42
app/ws/routes/fs.mkdir.js
Normal file
42
app/ws/routes/fs.mkdir.js
Normal file
@ -0,0 +1,42 @@
|
||||
const { Errors } = require('../../shared')
|
||||
const { NodeDescriptorType } = require('../../enum')
|
||||
|
||||
module.exports = exports = async (message, di) => {
|
||||
const Node = di.models.get('fs:Node')
|
||||
const { path, mode } = message.data()
|
||||
|
||||
if ( !path ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeDoesNotExist)
|
||||
)
|
||||
}
|
||||
|
||||
if ( path === '/' ) {
|
||||
return message.send_response(
|
||||
message.fresh.error(Errors.NodeAlreadyExists)
|
||||
)
|
||||
}
|
||||
|
||||
const existing_node = await Node.get_path(path, message.socket.session.overlay_name || 'mainline')
|
||||
if ( existing_node ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeAlreadyExists)
|
||||
)
|
||||
}
|
||||
|
||||
const [pied_parent_path, pied_name] = Node.path_parts(path)
|
||||
const node = new Node({
|
||||
pied_name,
|
||||
pied_parent_path,
|
||||
overlay_name: message.socket.session.overlay_name || 'mainline',
|
||||
mode: 16877, // TODO account for the mode from the client!
|
||||
descriptor_type: NodeDescriptorType.Directory,
|
||||
size: 100,
|
||||
})
|
||||
|
||||
await node.save()
|
||||
|
||||
message.send_response(
|
||||
message.fresh().data({ node: node.to_api() })
|
||||
)
|
||||
}
|
33
app/ws/routes/fs.open.js
Normal file
33
app/ws/routes/fs.open.js
Normal file
@ -0,0 +1,33 @@
|
||||
const { Errors } = require('../../shared')
|
||||
const { NodeDescriptorType } = require('../../enum')
|
||||
|
||||
module.exports = exports = async (message, di) => {
|
||||
const send_error = (err) => message.send_response(
|
||||
message.fresh().error(err)
|
||||
)
|
||||
|
||||
const new_fd = (node_uuid) => {
|
||||
if ( !message.socket.session.last_file_descriptor ) {
|
||||
message.socket.session.last_file_descriptor = 0
|
||||
message.socket.session.file_descriptors = {}
|
||||
}
|
||||
|
||||
message.socket.session.last_file_descriptor += 1
|
||||
message.socket.session.file_descriptors[message.socket.session.last_file_descriptor] = node_uuid
|
||||
return message.socket.session.last_file_descriptor
|
||||
}
|
||||
|
||||
const Node = di.models.get('fs:Node')
|
||||
const { path, flags } = message.data()
|
||||
|
||||
if ( !path ) return send_error(Errors.NodeDoesNotExist)
|
||||
if ( path === '/' ) return send_error(Errors.IsDirectoryDescriptor)
|
||||
|
||||
const node = await Node.get_path(path, message.socket.session.overlay_name || 'mainline')
|
||||
if ( !node ) send_error(Errors.NodeDoesNotExist)
|
||||
if ( node.descriptor_type === NodeDescriptorType.Directory ) send_error(Errors.IsDirectoryDescriptor)
|
||||
|
||||
message.send_response(
|
||||
message.fresh().data({ node: node.to_api(), descriptor: new_fd(node.uuid) })
|
||||
)
|
||||
}
|
23
app/ws/routes/fs.readdir.js
Normal file
23
app/ws/routes/fs.readdir.js
Normal file
@ -0,0 +1,23 @@
|
||||
const { Errors } = require('../../shared')
|
||||
|
||||
module.exports = exports = async (message, di) => {
|
||||
const Node = di.models.get('fs:Node')
|
||||
const { path } = message.data()
|
||||
|
||||
if ( path !== '/' ) {
|
||||
// If the path isn't the root of the workspace, then make
|
||||
// sure that the parent node actually exists
|
||||
const node = await Node.get_path(path, message.socket.session.overlay_name || 'mainline')
|
||||
if ( !node ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeDoesNotExist)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const nodes = await Node.list_path(path, message.socket.session.overlay_name || 'mainline')
|
||||
|
||||
message.send_response(
|
||||
message.fresh().data({ nodes })
|
||||
)
|
||||
}
|
11
app/ws/routes/fs.release.js
Normal file
11
app/ws/routes/fs.release.js
Normal file
@ -0,0 +1,11 @@
|
||||
module.exports = exports = async (message, di) => {
|
||||
const { descriptor } = message.data()
|
||||
|
||||
if ( message.socket.session.file_descriptors ) {
|
||||
delete message.socket.session.file_descriptors[descriptor]
|
||||
}
|
||||
|
||||
message.send_response(
|
||||
message.fresh()
|
||||
)
|
||||
}
|
69
app/ws/routes/fs.rename.js
Normal file
69
app/ws/routes/fs.rename.js
Normal file
@ -0,0 +1,69 @@
|
||||
const { Errors } = require('../../shared')
|
||||
const { NodeDescriptorType } = require('../../enum')
|
||||
|
||||
module.exports = exports = async (message, di) => {
|
||||
const Node = di.models.get('fs:Node')
|
||||
const { source, destination } = message.data()
|
||||
|
||||
if ( !source ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeDoesNotExist)
|
||||
)
|
||||
}
|
||||
|
||||
if ( source === '/' ) {
|
||||
return message.send_response(
|
||||
message.fresh.error(Errors.NodePermissionFail)
|
||||
)
|
||||
}
|
||||
|
||||
const node = await Node.get_path(source, message.socket.session.overlay_name || 'mainline')
|
||||
if ( !node ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeDoesNotExist)
|
||||
)
|
||||
}
|
||||
|
||||
// make sure the move-to destination is valid
|
||||
const [pied_parent_path, pied_name] = Node.path_parts(destination)
|
||||
if ( pied_parent_path !== '/' ) {
|
||||
const parent_node = await Node.get_path(pied_parent_path, message.socket.session.overlay_name || 'mainline')
|
||||
if ( !parent_node ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeDoesNotExist)
|
||||
)
|
||||
}
|
||||
|
||||
if ( parent_node.descriptor_type !== NodeDescriptorType.Directory ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NotDirectoryDescriptor)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const existing_target = await Node.get_path(destination, message.socket.session.overlay_name || 'mainline')
|
||||
if ( existing_target ) {
|
||||
// If we're moving to an existing target, overwrite that target
|
||||
const all_desc = await existing_target.all_descendants(message.socket.session.overlay_name || 'mainline')
|
||||
await Promise.all(all_desc.map(desc => {
|
||||
desc.deleted = true
|
||||
return desc.save()
|
||||
}))
|
||||
|
||||
existing_target.deleted = true
|
||||
await existing_target.save()
|
||||
}
|
||||
const moved_desc = await node.all_descendants(message.socket.session.overlay_name || 'mainline')
|
||||
await Promise.all(moved_desc.map(moved_node => {
|
||||
moved_node.pied_parent_path = moved_node.pied_parent_path.replace(source, destination)
|
||||
return moved_node.save()
|
||||
}))
|
||||
|
||||
node.pied_parent_path = pied_parent_path
|
||||
node.pied_name = pied_name
|
||||
await node.save()
|
||||
|
||||
message.send_response(
|
||||
message.fresh()
|
||||
)
|
||||
}
|
51
app/ws/routes/fs.rmdir.js
Normal file
51
app/ws/routes/fs.rmdir.js
Normal file
@ -0,0 +1,51 @@
|
||||
const { Errors } = require('../../shared')
|
||||
const { NodeDescriptorType } = require('../../enum')
|
||||
|
||||
module.exports = exports = async (message, di) => {
|
||||
const Node = di.models.get('fs:Node')
|
||||
const { path } = message.data()
|
||||
|
||||
if ( !path ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeDoesNotExist)
|
||||
)
|
||||
}
|
||||
|
||||
if ( path === '/' ) {
|
||||
return message.send_response(
|
||||
message.fresh.error(Errors.NodePermissionFail)
|
||||
)
|
||||
}
|
||||
|
||||
const node = await Node.get_path(path, message.socket.session.overlay_name || 'mainline')
|
||||
if ( !node ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeDoesNotExist)
|
||||
)
|
||||
}
|
||||
|
||||
if ( node.descriptor_type !== NodeDescriptorType.Directory ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NotDirectoryDescriptor)
|
||||
)
|
||||
}
|
||||
|
||||
const child = await Node.findOne({
|
||||
pied_parent_path: path,
|
||||
overlay_name: message.socket.session.overlay_name || 'mainline',
|
||||
deleted: false,
|
||||
})
|
||||
|
||||
if ( child ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeNotEmpty)
|
||||
)
|
||||
}
|
||||
|
||||
node.deleted = true
|
||||
await node.save()
|
||||
|
||||
message.send_response(
|
||||
message.fresh()
|
||||
)
|
||||
}
|
39
app/ws/routes/fs.unlink.js
Normal file
39
app/ws/routes/fs.unlink.js
Normal file
@ -0,0 +1,39 @@
|
||||
const { Errors } = require('../../shared')
|
||||
const { NodeDescriptorType } = require('../../enum')
|
||||
|
||||
module.exports = exports = async (message, di) => {
|
||||
const Node = di.models.get('fs:Node')
|
||||
const { path } = message.data()
|
||||
|
||||
if ( !path ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeDoesNotExist)
|
||||
)
|
||||
}
|
||||
|
||||
if ( path === '/' ) {
|
||||
return message.send_response(
|
||||
message.fresh.error(Errors.NodePermissionFail)
|
||||
)
|
||||
}
|
||||
|
||||
const node = await Node.get_path(path, message.socket.session.overlay_name || 'mainline')
|
||||
if ( !node ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeDoesNotExist)
|
||||
)
|
||||
}
|
||||
|
||||
if ( node.descriptor_type !== NodeDescriptorType.File ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.IsDirectoryDescriptor)
|
||||
)
|
||||
}
|
||||
|
||||
node.deleted = true
|
||||
await node.save()
|
||||
|
||||
message.send_response(
|
||||
message.fresh()
|
||||
)
|
||||
}
|
19
app/ws/routes/meta.authenticate.js
Normal file
19
app/ws/routes/meta.authenticate.js
Normal file
@ -0,0 +1,19 @@
|
||||
module.exports = exports = async (message, di) => {
|
||||
const Token = di.models.get('Token')
|
||||
const { token_value } = message.data()
|
||||
|
||||
const token = await Token.findOne({ active: true, token_value })
|
||||
if ( token ) {
|
||||
message.socket.session.is_auth = true
|
||||
message.socket.session.token_value = token_value
|
||||
message.socket.session.user_uuid = token.user_uuid
|
||||
|
||||
return message.send_response(
|
||||
message.fresh().data({ is_auth: true })
|
||||
)
|
||||
}
|
||||
|
||||
return message.send_response(
|
||||
message.fresh().data({ is_auth: false })
|
||||
)
|
||||
}
|
4
app/ws/routes/meta.ping.js
Normal file
4
app/ws/routes/meta.ping.js
Normal file
@ -0,0 +1,4 @@
|
||||
module.exports = exports = async (message, di) => {
|
||||
console.log('Received ping!');
|
||||
message.send_response(message.fresh())
|
||||
}
|
34
app/ws/routes/stream.getfd.js
Normal file
34
app/ws/routes/stream.getfd.js
Normal file
@ -0,0 +1,34 @@
|
||||
const { NodeDescriptorType } = require('../../enum')
|
||||
const { Errors } = require('../../shared')
|
||||
|
||||
module.exports = exports = async (message, di) => {
|
||||
const Node = di.models.get('fs:Node')
|
||||
const { descriptor } = message.data()
|
||||
|
||||
if ( !descriptor || !message.socket.session.file_descriptors[descriptor] ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NoSuchDescriptor)
|
||||
)
|
||||
}
|
||||
|
||||
const node = await Node.findOne({
|
||||
deleted: false,
|
||||
descriptor_type: NodeDescriptorType.File,
|
||||
uuid: message.socket.session.file_descriptors[descriptor],
|
||||
})
|
||||
|
||||
if ( !node ) {
|
||||
return message.send_response(
|
||||
message.fresh().error(Errors.NodeDoesNotExist)
|
||||
)
|
||||
}
|
||||
|
||||
const data = {
|
||||
node_uuid: node.uuid,
|
||||
socket_uuid: message.socket.uuid,
|
||||
}
|
||||
|
||||
message.send_response(
|
||||
message.fresh().data(data)
|
||||
)
|
||||
}
|
21
config/app.config.js
Normal file
21
config/app.config.js
Normal file
@ -0,0 +1,21 @@
|
||||
const app_config = {
|
||||
|
||||
/*
|
||||
* The name of the application.
|
||||
* Used through-out the application as the proper display name.
|
||||
*/
|
||||
name: env('APP_NAME', 'Flitter'),
|
||||
|
||||
/*
|
||||
* URL of the application.
|
||||
* Can be used to generate fully-qualified links.
|
||||
*/
|
||||
url: env('APP_URL', 'http://localhost:8000/'),
|
||||
|
||||
/*
|
||||
* The default locale that new users to the site will be assigned.
|
||||
*/
|
||||
default_locale: env('APP_DEFAULT_LOCALE', 'en_US'),
|
||||
}
|
||||
|
||||
module.exports = app_config
|
34
config/database.config.js
Normal file
34
config/database.config.js
Normal file
@ -0,0 +1,34 @@
|
||||
const database_config = {
|
||||
|
||||
/*
|
||||
* The name of the database.
|
||||
*/
|
||||
name: env('DATABASE_NAME', 'flitter'),
|
||||
|
||||
/*
|
||||
* The hostname of the database server.
|
||||
* Should be fully-qualified or resolvable.
|
||||
*/
|
||||
host: env('DATABASE_HOST', 'localhost'),
|
||||
|
||||
/*
|
||||
* MongoDB port on the database host.
|
||||
*/
|
||||
port: env('DATABASE_PORT', 27017),
|
||||
|
||||
auth: {
|
||||
|
||||
/*
|
||||
* Boolean true if the database connection requires auth.
|
||||
*/
|
||||
require: env('DATABASE_AUTH', false),
|
||||
|
||||
/*
|
||||
* MongoDB username and password.
|
||||
*/
|
||||
username: env('DATABASE_USERNAME', ''),
|
||||
password: env('DATABASE_PASSWORD', ''),
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = database_config
|
96
config/server.config.js
Normal file
96
config/server.config.js
Normal file
@ -0,0 +1,96 @@
|
||||
const server_config = {
|
||||
|
||||
/*
|
||||
* The server port.
|
||||
* Currently, Flitter supports HTTP/S natively.
|
||||
*/
|
||||
port: env('SERVER_PORT', 80),
|
||||
stream_port: env('SERVER_STREAM_PORT', 5746),
|
||||
|
||||
/*
|
||||
* The type of environment the application is running in.
|
||||
* Usually, either "production" or "development".
|
||||
* Development mode may cause the application to output extra
|
||||
* debugging information not secure enough for production.
|
||||
*/
|
||||
environment: env('ENVIRONMENT', 'production'),
|
||||
|
||||
data_dir: env('PIED_DATA_DIR', 'data'),
|
||||
|
||||
logging: {
|
||||
|
||||
/*
|
||||
* The logging level. Usually, 1-4.
|
||||
* The higher the level, the more information is logged.
|
||||
*/
|
||||
level: env('LOGGING_LEVEL', 2),
|
||||
|
||||
/*
|
||||
* If true, API responses will be logged to the database.
|
||||
*/
|
||||
api_logging: env('LOG_API_RESPONSES', false),
|
||||
|
||||
/*
|
||||
* If true, caught request errors will be logged to the database.
|
||||
*/
|
||||
error_logging: env('LOG_REQUEST_ERRORS', true),
|
||||
},
|
||||
|
||||
session: {
|
||||
|
||||
/*
|
||||
* The secret used to encrypt the session.
|
||||
* This should be set in the environment.
|
||||
*/
|
||||
secret: env('SECRET', 'changeme'),
|
||||
|
||||
/*
|
||||
* The max age (in milliseconds) of the session cookie.
|
||||
* If undefined, the max-age will be set to "Session" so it is
|
||||
* cleared when the browser exits.
|
||||
*/
|
||||
max_age: env('SESSION_MAX_AGE', undefined),
|
||||
},
|
||||
|
||||
uploads: {
|
||||
/*
|
||||
* If true, the server will accept files uploaded in the request.
|
||||
*/
|
||||
enable: env('SERVER_ENABLE_UPLOADS', false),
|
||||
|
||||
/*
|
||||
* Regex to match routes that file uploads are accepted from.
|
||||
* By default, this accepts files uploaded on all routes.
|
||||
*/
|
||||
allowed_path: /./,
|
||||
|
||||
/*
|
||||
* Path for uploaded files.
|
||||
* Should be relative to the application root.
|
||||
*
|
||||
* Note that this is NOT the config for flitter-upload.
|
||||
*/
|
||||
destination: './tmp.uploads'
|
||||
},
|
||||
|
||||
ssl: {
|
||||
/*
|
||||
* If true, the server will be HTTPS, not HTTP.
|
||||
*/
|
||||
enable: env('SSL_ENABLE', false),
|
||||
|
||||
/*
|
||||
* Path to your domain's certificate file.
|
||||
* This should contain any intermediate certificates as well.
|
||||
*/
|
||||
cert_file: env('SSL_CERT_FILE', 'cert.pem'),
|
||||
|
||||
/*
|
||||
* Path to your domain's certificate key.
|
||||
*/
|
||||
key_file: env('SSL_KEY_FILE', 'cert.key'),
|
||||
},
|
||||
|
||||
}
|
||||
|
||||
module.exports = server_config
|
45
config/upload.config.js
Normal file
45
config/upload.config.js
Normal file
@ -0,0 +1,45 @@
|
||||
/*
|
||||
* flitter-upload configuration
|
||||
* ---------------------------------------------------------------
|
||||
* Specifies the configuration for various uploader aspects. Mainly,
|
||||
* contains the configuration for the different file upload backends.
|
||||
*/
|
||||
const upload_config = {
|
||||
/*
|
||||
* The name of the upload backend to use by default.
|
||||
*/
|
||||
default_store: env('UPLOAD_DEFAULT_STORE', 's3'),
|
||||
enabled: true,
|
||||
|
||||
/*
|
||||
* Stores available to the uploader.
|
||||
*/
|
||||
stores: {
|
||||
|
||||
/*
|
||||
* Example of an Amazon AWS S3 (or compatible) uploader.
|
||||
*/
|
||||
s3: {
|
||||
enabled: true,
|
||||
|
||||
// Backed by an S3 bucket.
|
||||
type: 'AWSS3Store',
|
||||
|
||||
// AWS SDK credentials and configuration
|
||||
aws: {
|
||||
region: env('AWS_REGION', 'us-east-1'),
|
||||
key_id: env('AWS_ACCESS_KEY_ID'),
|
||||
key_secret: env('AWS_SECRET_ACCESS_KEY'),
|
||||
endpoint: env('AWS_S3_ENDPOINT'),
|
||||
},
|
||||
|
||||
// The bucket to which the files should be uploaded
|
||||
bucket: env('AWS_UPLOAD_BUCKET', 'flitter.localhost'),
|
||||
|
||||
// The prefix to be used for file uploads
|
||||
prefix: env('AWS_UPLOAD_PREFIX', ''),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
module.exports = exports = upload_config
|
0
data/.gitkeep
Normal file
0
data/.gitkeep
Normal file
25
example.env
Normal file
25
example.env
Normal file
@ -0,0 +1,25 @@
|
||||
APP_NAME=Flitter
|
||||
|
||||
SERVER_PORT=8000
|
||||
|
||||
# 1 - Error/Success
|
||||
# 2 - Warning
|
||||
# 3 - Message
|
||||
# 4 - Info
|
||||
# 5 - Debug
|
||||
LOGGING_LEVEL=10
|
||||
|
||||
DATABASE_HOST=127.0.0.1
|
||||
DATABASE_PORT=27017
|
||||
DATABASE_NAME=pied_d1
|
||||
|
||||
# if true, specify DATABASE_USERNAME and DATABASE_PASSWORD
|
||||
DATABASE_AUTH=false
|
||||
|
||||
# used to hash passwords and session keys
|
||||
# should be randomly generated - require('uuid/v4')()
|
||||
SECRET=changeme
|
||||
|
||||
# production | development
|
||||
# if development, errors are displayed in detail
|
||||
ENVIRONMENT=production
|
23
flitter
Executable file
23
flitter
Executable file
@ -0,0 +1,23 @@
|
||||
#!/usr/bin/env node
|
||||
/*
|
||||
* ./flitter
|
||||
* -------------------------------------------------------------
|
||||
* The ./flitter command is used to interact with Flitter and its tools
|
||||
* in the development environment. Currently, it lends access to Flitter
|
||||
* shell, which is like a Node interactive prompt, but it's launched from
|
||||
* within the same context as the Flitter HTTP server, allowing developers
|
||||
* to interact with Flitter directly.
|
||||
*/
|
||||
const CliAppUnit = require('flitter-cli/CliAppUnit')
|
||||
const units = require('./Units.flitter')
|
||||
|
||||
/*
|
||||
* Replace the HTTP server application target with the CLI handler.
|
||||
*/
|
||||
units.App = CliAppUnit
|
||||
|
||||
const { FlitterApp, RunLevelErrorHandler } = require('libflitter')
|
||||
const flitter = new FlitterApp(units)
|
||||
const rleh = new RunLevelErrorHandler()
|
||||
|
||||
flitter.run().catch(rleh.handle) // calls up() and down()
|
43
index.js
Normal file
43
index.js
Normal file
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Load the units file.
|
||||
* -------------------------------------------------------------
|
||||
* This file contains an ordered object of unit files. Flitter will load these
|
||||
* one at a time to launch the application. Each unit in the sequence is passed
|
||||
* the function for the next unit in the sequence. This forms the function stack
|
||||
* by chaining the units together, ending with the Flitter App unit.
|
||||
*/
|
||||
const units = require('./Units.flitter')
|
||||
|
||||
const { FlitterApp, RunLevelErrorHandler } = require('libflitter')
|
||||
|
||||
/*
|
||||
* Create the app.
|
||||
* -------------------------------------------------------------
|
||||
* The FlitterApp object contains the wrapper for the Express app, as well as
|
||||
* the initialization function that chains together the individual units. This
|
||||
* is why we pass it the units.
|
||||
*/
|
||||
const flitter = new FlitterApp(units)
|
||||
|
||||
/*
|
||||
* Create the error handler.
|
||||
* -------------------------------------------------------------
|
||||
* The run-level error handler is responsible for dealing with errors that make
|
||||
* it all the way to the top level of the running app. Most of the time, routing
|
||||
* errors are handled by the router and result in some kind of error page showing
|
||||
* to the user.
|
||||
*
|
||||
* The errors handled by the RLEH are structural errors that are problems with the
|
||||
* application itself.
|
||||
*/
|
||||
const rleh = new RunLevelErrorHandler()
|
||||
|
||||
/*
|
||||
* Launch the server.
|
||||
* -------------------------------------------------------------
|
||||
* This calls the first unit in the unit chain. This chain ends with the Flitter
|
||||
* server component which launches the Node HTTP server.
|
||||
*
|
||||
* Calls up() and down().
|
||||
*/
|
||||
flitter.run().catch(rleh.handle)
|
34
package.json
Normal file
34
package.json
Normal file
@ -0,0 +1,34 @@
|
||||
{
|
||||
"name": "flitter",
|
||||
"version": "0.1.0",
|
||||
"description": "Flitter is a simple MVC framework wrapper for Express.js.",
|
||||
"main": "index.js",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://git.garrettmills.dev/flitter/flitter"
|
||||
},
|
||||
"keywords": [
|
||||
"flitter",
|
||||
"glmdev",
|
||||
"framework",
|
||||
"express"
|
||||
],
|
||||
"author": "Garrett Mills <garrett@glmdev.tech> (https://garrettmills.dev/)",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"express-ws": "^4.0.0",
|
||||
"flitter-auth": "^0.19.1",
|
||||
"flitter-cli": "^0.16.0",
|
||||
"flitter-di": "^0.5.0",
|
||||
"flitter-flap": "^0.5.2",
|
||||
"flitter-forms": "^0.8.1",
|
||||
"flitter-i18n": "^0.1.0",
|
||||
"flitter-orm": "^0.4.0",
|
||||
"flitter-upload": "^0.10.0",
|
||||
"libflitter": "^0.58.1",
|
||||
"stream-skip": "^1.0.3",
|
||||
"tmp-promise": "^3.0.2",
|
||||
"websocket-stream": "^5.5.2",
|
||||
"ws": "^7.4.0"
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user