Import framework

This commit is contained in:
Garrett Mills 2022-09-26 11:26:31 -05:00
parent d75e32b316
commit dff487a54f
25 changed files with 3541 additions and 0 deletions

0
CONTRIBUTING.md Normal file
View File

137
ex Executable file
View File

@ -0,0 +1,137 @@
#!/bin/bash
ENV_NODE="$(which node)"
ENV_PNPM="$(which pnpm)"
LOGO=" _
/ /\ ______ _ _ _
/ / \ | ____| | | | | |
/ / /\ \ | |__ __ _| |_ ___ | | | ___
/ / /\ \ \ | __| \ \/ / __/ _ \| | |/ _ \\
/ / / \ \_\ | |____ > <| || (_) | | | (_) |
\/_/ \/_/ |______/_/\_\\\__\___/|_|_|\___/
"
# Author: Tasos Latsas
# spinner.sh
#
# Display an awesome 'spinner' while running your long shell commands
#
# Do *NOT* call _spinner function directly.
# Use {start,stop}_spinner wrapper functions
# usage:
# 1. source this script in your's
# 2. start the spinner:
# start_spinner [display-message-here]
# 3. run your command
# 4. stop the spinner:
# stop_spinner [your command's exit status]
#
# Also see: test.sh
function _spinner() {
# $1 start/stop
#
# on start: $2 display message
# on stop : $2 process exit status
# $3 spinner function pid (supplied from stop_spinner)
local on_success="DONE"
local on_fail="FAIL"
local white="\e[1;37m"
local green="\e[1;32m"
local red="\e[1;31m"
local nc="\e[0m"
case $1 in
start)
# calculate the column where spinner and status msg will be displayed
let column=$(tput cols)-${#2}-8
# display message and position the cursor in $column column
echo -ne ${2}
printf "%${column}s"
# start spinner
i=1
sp='\|/-'
delay=${SPINNER_DELAY:-0.15}
while :
do
printf "\b${sp:i++%${#sp}:1}"
sleep $delay
done
;;
stop)
if [[ -z ${3} ]]; then
echo "spinner is not running.."
exit 1
fi
kill $3 > /dev/null 2>&1
# inform the user uppon success or failure
echo -en "\b["
if [[ $2 -eq 0 ]]; then
echo -en "${green}${on_success}${nc}"
else
echo -en "${red}${on_fail}${nc}"
fi
echo -e "]"
;;
*)
echo "invalid argument, try {start/stop}"
exit 1
;;
esac
}
function start_spinner {
# $1 : msg to display
_spinner "start" "${1}" &
# set global spinner pid
_sp_pid=$!
disown
}
function stop_spinner {
# $1 : command exit status
_spinner "stop" $1 $_sp_pid
unset _sp_pid
}
function echoRun() {
echo ""
echo "+ $@"
echo ""
"$@"
echo ""
}
if [ ! -d "./node_modules" ]; then
echo "$LOGO"
echo "+----------------------------------------+"
echo "| Docs: https://extollo.garrettmills.dev |"
echo "+----------------------------------------+"
echo ""
echo "Welcome to Extollo! Let's set things up for the first time..."
if [ ! -x "$ENV_PNPM" ]; then
echo "Please install PNPM to use Extollo."
fi
echoRun "$ENV_PNPM" i
if [ ! -f "./.env" ]; then
echoRun cp example.env .env
fi
echo ""
echo ""
printf "\033[32m✓\033[39m Looks like you're all set up! Run this command again to access the Extollo CLI.\n"
else
"$ENV_PNPM" cli "$@"
fi

4
example.env Normal file
View File

@ -0,0 +1,4 @@
DEBUG_MODE=true
EXTOLLO_LOGGING_LEVEL=6
DATABASE_PASSWORD=extollo
DATABASE_HOST=db01.platform.local

47
package.json Normal file
View File

@ -0,0 +1,47 @@
{
"name": "@extollo/extollo",
"version": "0.1.0",
"description": "The framework lifts up your code.",
"main": "lib/index.js",
"types": "lib/index.d.ts",
"directories": {
"lib": "lib"
},
"dependencies": {
"@atao60/fse-cli": "^0.1.7",
"@extollo/lib": "^0.11.0",
"copyfiles": "^2.4.1",
"feed": "^4.2.2",
"gotify": "^1.1.0",
"rimraf": "^3.0.2",
"ts-expose-internals": "^4.5.4",
"ts-node": "^10.9.0",
"ts-patch": "^2.0.1",
"ts-to-zod": "^1.8.0",
"typescript": "^4.3.2",
"zod": "^3.11.6"
},
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"app": "ts-node src/index.ts",
"cli": "ts-node src/cli.ts",
"watch": "nodemon --ext js,pug,ts --watch src --exec 'ts-node src/index.ts'",
"clean": "rimraf lib",
"prepare": "pnpm run build",
"build": "pnpm run clean && tsc -p tsconfig.json && fse copy --all --dereference --preserveTimestamps --keepExisting=false --quiet --errorOnExist=false src/app/resources lib/app/resources"
},
"files": [
"lib/**/*"
],
"prepare": "npm run build",
"postversion": "git push && git push --tags",
"repository": {
"type": "git",
"url": "https://code.garrettmills.dev/extollo/extollo"
},
"author": "garrettmills <shout@garrettmills.dev>",
"license": "MIT",
"devDependencies": {
"nodemon": "^2.0.19"
}
}

2946
pnpm-lock.yaml Normal file

File diff suppressed because it is too large Load Diff

48
src/Units.extollo.ts Normal file
View File

@ -0,0 +1,48 @@
import {
Config,
Controllers,
HTTPServer,
Files,
Middlewares,
Routing,
Unit,
Database,
Models,
CommandLine,
Internationalization,
Authentication,
Discovery,
ValidationUnit,
Migrations,
Queueables,
Redis,
Bus,
WebsocketServer,
} from '@extollo/lib'
import {AppUnit} from './app/AppUnit'
Error.stackTraceLimit = 500
export const Units = [
Config,
Redis,
Queueables,
Bus,
ValidationUnit,
Files,
CommandLine,
Controllers,
Middlewares,
Database,
Models,
Migrations,
Internationalization,
Authentication,
AppUnit,
Routing,
Discovery,
HTTPServer,
WebsocketServer,
] as (typeof Unit)[]

9
src/app/AppUnit.ts Normal file
View File

@ -0,0 +1,9 @@
import {ORMUser, Singleton, Unit} from '@extollo/lib'
import {User} from './models/User.model'
@Singleton()
export class AppUnit extends Unit {
async up(): Promise<void> {
this.container().registerStaticOverride(ORMUser, User)
}
}

View File

@ -0,0 +1,5 @@
import { env } from '@extollo/lib'
export default {
name: env('APP_NAME', 'Extollo'),
}

View File

@ -0,0 +1,21 @@
import {AuthenticationConfig, CoreIDLoginProvider, OAuth2LoginProviderConfig, ORMUserRepository, env} from '@extollo/lib'
const authConfig: AuthenticationConfig = {
storage: ORMUserRepository,
providers: {
coreid: {
driver: CoreIDLoginProvider,
config: {
default: true,
displayName: 'Starship CoreID',
clientId: env('COREID_CLIENT_ID'),
clientSecret: env('COREID_CLIENT_SECRET'),
loginUrl: env('COREID_BASE', '') + '/auth/service/oauth2/authorize?client_id=%c&redirect_uri=%r',
tokenUrl: env('COREID_BASE', '') + '/auth/service/oauth2/redeem',
userUrl: env('COREID_BASE', '') + '/api/v1/auth/users/me',
} as OAuth2LoginProviderConfig,
}
},
}
export default authConfig

View File

@ -0,0 +1,14 @@
import { env } from "@extollo/lib";
export default {
connections: {
default: {
user: env('DATABASE_USERNAME', 'extollo'),
password: env('DATABASE_PASSWORD'),
host: env('DATABASE_HOST', 'localhost'),
port: env('DATABASE_PORT', 5432),
database: env('DATABASE_NAME', 'extollo'),
dialect: env('DATABASE_DIALECT', 'postgres'),
},
},
}

View File

@ -0,0 +1,5 @@
import { env } from "@extollo/lib"
export default {
app_name: env('APP_NAME', 'Extollo'),
}

View File

@ -0,0 +1,6 @@
import {env} from '@extollo/lib'
export default {
welcome_to_extollo: 'Welcome to Extollo!',
viewed_page_num_times: 'You have viewed this page :num: times.',
}

View File

@ -0,0 +1,24 @@
import {OAuth2Client, OAuth2Scope, env, uuid4} from '@extollo/lib'
export default {
secret: env('OAUTH2_SECRET'),
scopes: {
'user-info': {
id: 'user-info',
name: 'user-info',
description: 'access basic information about your account',
},
} as {[key: string]: OAuth2Scope},
clients: {
'test-1': {
id: 'test-1',
display: 'Test 1',
secret: env('TEST_CLIENT_SECRET', uuid4()),
allowedFlows: ['code'],
allowedScopeIds: ['user-info'],
allowedRedirectUris: [
'http://localhost:1234/callback',
],
},
} as {[key: string]: OAuth2Client},
}

View File

@ -0,0 +1,66 @@
import {
env,
basePath,
ORMSession,
LocalFilesystem,
LocalFilesystemConfig,
RedisCache,
CacheQueue,
BusConnectorConfig, QueueConfig, SyncQueue
} from "@extollo/lib"
import {LogRequest} from "../http/middlewares/LogRequest.middleware";
export default {
debug: env('DEBUG_MODE', false),
session: {
/* The implementation of @extollo/lib.Session that serves as the session backend. */
driver: ORMSession,
},
bus: {
connectors: [
{type: 'redis'},
] as BusConnectorConfig[],
},
queue: {
driver: CacheQueue,
} as QueueConfig,
cache: {
driver: RedisCache,
},
/*
* Here, you can define various filesystem drivers that can be used in
* your application to store/retrieve files.
*
* The key in the object is the 'name' of the filesystem as it will be
* fetched in code. For example, if you have a `fubar: { ... }` item,
* then you can retrieve that filesystem using the Files service like
* so:
*
* files.getFilesystem('fubar') // => Filesystem { ... }
*/
filesystems: {
default: {
/* If true, this will serve as the default filesystem for modules in your application. */
isDefault: true,
/* The implementation of @extollo/lib.Filesystem that serves as the backend. */
driver: LocalFilesystem,
/* The config required by the filesystem driver. */
config: {
baseDir: basePath('..', 'uploads').toLocal,
} as LocalFilesystemConfig,
}
},
middleware: {
global: {
pre: [LogRequest],
},
},
}

View File

@ -0,0 +1,45 @@
import {Controller, view, Session, Inject, Injectable, Locale, Validator, WebSocketBus, JSONMessageEvent, Logging, Bus, WebSocketCloseEvent} from '@extollo/lib'
import {UserLogin} from "../../../types/UserLogin";
@Injectable()
export class Home extends Controller {
@Inject()
protected readonly session!: Session;
@Inject()
protected readonly locale!: Locale;
@Inject()
protected readonly logging!: Logging
public welcome() {
this.session.set('app_visits', this.session.get('app_visits', 0) + 1)
const valid = new Promise<UserLogin>(() => {})
return view('@extollo:welcome', {
app_visits: this.session.get('app_visits'),
locale: this.locale.helper(),
})
}
public async socket(ws: WebSocketBus): Promise<void> {
ws.subscribe(JSONMessageEvent, event => {
this.logging.info('Got JSON message event:')
this.logging.debug(event.value)
})
const interval = setInterval(() => {
ws.push(new JSONMessageEvent({ hello: 'world!' }))
}, 5000)
await this.request.make<Bus>(Bus)
.subscribe(WebSocketCloseEvent, () => {
clearInterval(interval)
})
}
public test() {
return new Validator<UserLogin>()
}
}

View File

@ -0,0 +1,11 @@
import {Inject, Injectable, Logging, Middleware} from '@extollo/lib'
@Injectable()
export class LogRequest extends Middleware {
@Inject()
protected readonly logging!: Logging
public async apply() {
this.logging.info(`Incoming request: ${this.request.method} @ ${this.request.path}`)
}
}

View File

@ -0,0 +1,31 @@
import {
Route,
SessionAuthMiddleware,
AuthRequiredMiddleware,
scope,
OAuth2Server,
TokenAuthMiddleware
} from '@extollo/lib'
import {Home} from '../controllers/main/Home.controller'
Route.group('/', () => {
Route.get('/')
.calls<Home>(Home, home => home.welcome)
})
Route.group('', () => {
Route.get('/dash')
.pre(AuthRequiredMiddleware)
.calls<Home>(Home, home => home.welcome)
}).pre(SessionAuthMiddleware)
OAuth2Server.routes()
Route.group('', () => {
Route.get('tok')
.pre(scope('tok'))
.handledBy(() => 'Token!')
}).pre(TokenAuthMiddleware)
Route.socket('/ws/hello')
.calls<Home>(Home, home => home.socket)

View File

@ -0,0 +1,12 @@
import {Injectable, BaseJob} from '@extollo/lib'
@Injectable()
export default class LogMessage extends BaseJob {
async execute(): Promise<void> {
this.logging.info('Executing LogMessage...')
await new Promise<void>(res => {
setTimeout(() => res(), 3000)
})
this.logging.success('The LogMessage job has executed!')
}
}

View File

@ -0,0 +1,5 @@
import {ORMUser} from '@extollo/lib'
export class User extends ORMUser {
}

View File

@ -0,0 +1,6 @@
html
head
title !{locale('app_name')}
body
h1 !{locale('welcome_to_extollo')}
h2 !{locale('viewed_page_num_times', { interp: { num: app_visits } })}

View File

@ -0,0 +1,14 @@
export interface UserLogin {
/**
* @minLength 1
* @maxLength 100
*/
username: string,
/**
* @minLength 1
* @maxLength 100
*/
password: string,
}

42
src/bootstrap.ts Normal file
View File

@ -0,0 +1,42 @@
import {Application, CommandLineApplication, Foreground} from '@extollo/lib'
import {Units} from './Units.extollo'
/*
* Helper functions to bootstrap the Application instance for different uses.
*/
/**
* Get the base application with no final-target unit.
*/
export function base(): Application {
const app = Application.getApplication()
app.scaffold(__dirname, Units.slice(0, -1))
return app
}
/**
* Get the application with the final unit replaced with the CommandLineApplication
* for use on the CLI.
*/
export function cli(): Application {
const app = Application.getApplication()
app.forceStartupMessage = false
const units = [...Units, CommandLineApplication]
app.scaffold(__dirname, units)
return app
}
/**
* Get the application as it should be run normally.
*/
export function app(): Application {
const app = Application.getApplication()
const units = [...Units]
units.push(Foreground)
app.scaffold(__dirname, units)
return app
}

15
src/cli.ts Normal file
View File

@ -0,0 +1,15 @@
#!/usr/bin/env -S node --experimental-repl-await
import {globalRegistry} from '@extollo/lib'
import {cli} from './bootstrap'
globalRegistry.run(async () => {
/*
* The Application
* -----------------------------------------------------
* The application instance is a global inversion of control container that
* ties your entire application together. The app container manages services
* and lifecycle.
*/
const app = cli()
await app.run()
})

13
src/index.ts Normal file
View File

@ -0,0 +1,13 @@
import {globalRegistry} from '@extollo/lib'
import {app} from './bootstrap'
globalRegistry.run(async () => {
/*
* The Application
* -----------------------------------------------------
* The application instance is a global inversion of control container that
* ties your entire application together. The app container manages services
* and lifecycle.
*/
await app().run()
})

15
tsconfig.json Normal file
View File

@ -0,0 +1,15 @@
{
"compilerOptions": {
"target": "esnext",
"module": "commonjs",
"declaration": true,
"outDir": "./lib",
"strict": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"skipLibCheck": true,
"lib": ["esnext", "dom", "dom.iterable"],
"preserveSymlinks": true
},
"include": ["src"]
}