Initial import

master
Garrett Mills 3 years ago
commit 81032e3b0b
Signed by: garrettmills
GPG Key ID: D2BF5FBA8298F246

90
.gitignore vendored

@ -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

@ -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.

@ -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

@ -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

@ -0,0 +1,6 @@
const NodeDescriptorType = {
File: 'file',
Directory: 'directory',
}
module.exports = exports = { NodeDescriptorType }

@ -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

@ -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

@ -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

@ -0,0 +1,4 @@
module.exports = exports = {
Errors: require('../../shared/Errors'),
Message: require('../../shared/Message'),
}

@ -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

@ -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;

@ -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) })
)
}

@ -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 })
)
}

@ -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() })
)
}

@ -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) })
)
}

@ -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 })
)
}

@ -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()
)
}

@ -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()
)
}

@ -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()
)
}

@ -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()
)
}

@ -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 })
)
}

@ -0,0 +1,4 @@
module.exports = exports = async (message, di) => {
console.log('Received ping!');
message.send_response(message.fresh())
}

@ -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)
)
}

@ -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

@ -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

@ -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

@ -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,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

@ -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()

@ -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)

@ -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"
}
}

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save