chore: setup featherjs project

This commit is contained in:
Elias Renman
2023-05-20 09:18:45 +02:00
parent ab69ce119a
commit 0040dc6384
26 changed files with 762 additions and 0 deletions

121
server/.gitignore vendored Normal file
View File

@@ -0,0 +1,121 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# 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
*.lcov
# 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/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.production
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.sqlite
lib/

121
server/.npmignore Normal file
View File

@@ -0,0 +1,121 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# 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
*.lcov
# 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/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
.env.production
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.*
.sqlite
lib/

9
server/.prettierrc Normal file
View File

@@ -0,0 +1,9 @@
{
"tabWidth": 2,
"useTabs": false,
"printWidth": 110,
"semi": false,
"trailingComma": "none",
"singleQuote": true,
"parser": "typescript"
}

View File

@@ -0,0 +1,10 @@
{
"port": {
"__name": "PORT",
"__format": "number"
},
"host": "HOSTNAME",
"authentication": {
"secret": "FEATHERS_SECRET"
}
}

View File

@@ -0,0 +1,17 @@
{
"host": "localhost",
"port": 3030,
"public": "./public/",
"origins": [
"http://localhost:3030"
],
"paginate": {
"default": 10,
"max": 50
},
"sqlite": {
"client": "sqlite3",
"connection": "server.sqlite",
"useNullAsDefault": true
}
}

3
server/config/test.json Normal file
View File

@@ -0,0 +1,3 @@
{
"port": 8998
}

7
server/knexfile.ts Normal file
View File

@@ -0,0 +1,7 @@
// For more information about this file see https://dove.feathersjs.com/guides/cli/databases.html
import { app } from './src/app'
// Load our database connection info from the app configuration
const config = app.get('sqlite')
module.exports = config

10
server/node_modules/.yarn-integrity generated vendored Normal file
View File

@@ -0,0 +1,10 @@
{
"systemParams": "linux-x64-108",
"modulesFolders": [],
"flags": [],
"linkedModules": [],
"topLevelPatterns": [],
"lockfileEntries": {},
"files": [],
"artifacts": {}
}

48
server/package.json Normal file
View File

@@ -0,0 +1,48 @@
{
"name": "server",
"description": "The server for handling two way communication with a iot Device",
"version": "0.0.0",
"homepage": "",
"private": true,
"keywords": [
"feathers"
],
"author": {},
"contributors": [],
"bugs": {},
"engines": {
"node": ">= 18.16.0"
},
"feathers": {
"language": "ts",
"packager": "yarn",
"database": "sqlite",
"framework": "koa",
"transports": [
"rest",
"websockets"
],
"schema": "typebox"
},
"directories": {
"lib": "src",
"test": "test"
},
"files": [
"lib/client.js",
"lib/**/*.d.ts",
"lib/**/*.shared.js"
],
"main": "lib/client",
"scripts": {
"dev": "nodemon -x ts-node src/index.ts",
"compile": "shx rm -rf lib/ && tsc",
"start": "node lib/",
"prettier": "npx prettier \"**/*.ts\" --write",
"mocha": "cross-env NODE_ENV=test mocha test/ --require ts-node/register --recursive --extension .ts --exit",
"test": "cross-env NODE_ENV=test npm run migrate && npm run mocha",
"bundle:client": "npm run compile && npm pack --pack-destination ./public",
"migrate": "knex migrate:latest",
"migrate:make": "knex migrate:make"
}
}

37
server/public/index.html Normal file
View File

@@ -0,0 +1,37 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>server</title>
<meta name="description" content="The server for handling two way communication with a iot Device">
<meta name="viewport" content="width=device-width, initial-scale=1">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
html {
height: 100%;
}
body {
min-height: 100%;
display: flex;
align-items: center;
}
img.logo {
display: block;
margin: auto auto;
width: 30%;
max-width: 100%;
max-height: 100%;
}
</style>
</head>
<body>
<img class="logo" src="data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjUwMCIgaGVpZ2h0PSIyNTAwIiB2aWV3Qm94PSIwIDAgMjU2IDI1NiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiBwcmVzZXJ2ZUFzcGVjdFJhdGlvPSJ4TWlkWU1pZCI+PHBhdGggZD0iTTEyOCA5LjEwMmM2NS42NjUgMCAxMTguODk4IDUzLjIzMyAxMTguODk4IDExOC44OTggMCA2NS42NjUtNTMuMjMzIDExOC44OTgtMTE4Ljg5OCAxMTguODk4QzYyLjMzNSAyNDYuODk4IDkuMTAyIDE5My42NjUgOS4xMDIgMTI4IDkuMTAyIDYyLjMzNSA2Mi4zMzUgOS4xMDIgMTI4IDkuMTAyTTEyOCAwQzU3LjQyMSAwIDAgNTcuNDIxIDAgMTI4YzAgNzAuNTc5IDU3LjQyMSAxMjggMTI4IDEyOCA3MC41NzkgMCAxMjgtNTcuNDIxIDEyOC0xMjhDMjU2IDU3LjQyMSAxOTguNTc5IDAgMTI4IDBtMjAuODMgMjUuNTI0Yy0xMC40My0xLjg5Ni0zNS42NTEgMzYuNDA5LTQzLjk5NCA1OS43MzQtLjYzNCAxLjc2OS0yLjA4NiA4LjI0OS0yLjA4NiA5Ljk1NSAwIDAgNi41MzEgMTQuMDU1IDguMzQzIDE3LjM1MS0zLjAzNC0xLjU4LTkuMzIzLTEzLjc1Ni05LjMyMy0xMy43NTYtMy4wMzQgNS43ODQtNS45NDIgMzIuMzQtNC45OTQgMzcuMjcxIDAgMCA2Ljc2MiAxMC4wNjIgOS4zODcgMTIuNTc4LTMuNjAzLTEuMjAxLTkuNjcxLTkuMzU1LTkuNjcxLTkuMzU1LTEuMTM4IDMuNTA4LS45MTYgMTAuODA3LS4zNzkgMTMuMjc0IDQuNTUxIDYuNjM3IDEwLjYxOSA3LjM5NiAxMC42MTkgNy4zOTZzLTYuNjM3IDY2LjE4MSAzLjQxMyA3MS4xMTFjNi4yNTgtMS4zMjcgNy43NzUtNzMuOTU2IDcuNzc1LTczLjk1NnM3LjU4NS41NjkgOS4yOTItMS4zMjdjMy44NTYtMi42NTUgMTIuODI2LTMwLjIyNCAxMi45NTgtMzQuMjAyIDAgMC0xMC40MSAxLjk1Mi0xNS40ODcgMy45MjQgMy44MjYtMy44IDE2LjA0OS02LjM1MiAxNi4wNDktNi4zNTIgMy4zMTUtMy45NzkgMTAuMjkxLTMxLjA0NyAxMC45OTQtMzkuMzkxLjE3Ni0yLjA5My41ODMtNC42NTcuMjY4LTguMzk4IDAgMC05Ljk0MSAyLjE3Ny0xMi4wMTQgMS40MjQgMi4xMDQtLjIzNyAxMi4yNjMtNC4xNCAxMi4yNjMtNC4xNCAxLjgwMS0xNi4yMTMgMi4zNTgtNDIuMDkxLTMuNDEzLTQzLjE0MXptLTM2LjM4IDE3MS42OTFjLS43OTUgMTkuNDk2LTEuMjk0IDI1LjAwNC0yLjExNSAyOS42MDEtLjM3OS44NTctLjc1OC45OTctMS4xMzgtLjA5NS0zLjQ3Ny0xNS45OTItMy4yMjQtMTM2LjQzOCAzNi40MDktMTkxLjI0MS0yMy4wNSA0Mi4wOTItMzMuNTM1IDEyMi44NjEtMzMuMTU2IDE2MS43MzV6IiBmaWxsPSIjMzMzIi8+PC9zdmc+" />
</body>
</html>

42
server/readme.md Normal file
View File

@@ -0,0 +1,42 @@
# server
> The server for handling two way communication with a iot Device
## About
This project uses [Feathers](http://feathersjs.com). An open source framework for building APIs and real-time applications.
## Getting Started
1. Make sure you have [NodeJS](https://nodejs.org/) and [npm](https://www.npmjs.com/) installed.
2. Install your dependencies
```
cd path/to/server
npm install
```
3. Start your app
```
npm run compile # Compile TypeScript source
npm run migrate # Run migrations to set up the database
npm start
```
## Testing
Run `npm test` and all your tests in the `test/` directory will be run.
## Scaffolding
This app comes with a powerful command line interface for Feathers. Here are a few things it can do:
```
$ npx feathers help # Show all commands
$ npx feathers generate service # Generate a new Service
```
## Help
For more information on all the things you can do with Feathers visit [docs.feathersjs.com](http://docs.feathersjs.com).

54
server/src/app.ts Normal file
View File

@@ -0,0 +1,54 @@
// For more information about this file see https://dove.feathersjs.com/guides/cli/application.html
import { feathers } from '@feathersjs/feathers'
import configuration from '@feathersjs/configuration'
import { koa, rest, bodyParser, errorHandler, parseAuthentication, cors, serveStatic } from '@feathersjs/koa'
import socketio from '@feathersjs/socketio'
import { configurationValidator } from './configuration'
import type { Application } from './declarations'
import { logError } from './hooks/log-error'
import { sqlite } from './sqlite'
import { services } from './services/index'
import { channels } from './channels'
const app: Application = koa(feathers())
// Load our app configuration (see config/ folder)
app.configure(configuration(configurationValidator))
// Set up Koa middleware
app.use(cors())
app.use(serveStatic(app.get('public')))
app.use(errorHandler())
app.use(parseAuthentication())
app.use(bodyParser())
// Configure services and transports
app.configure(rest())
app.configure(
socketio({
cors: {
origin: app.get('origins')
}
})
)
app.configure(channels)
app.configure(sqlite)
app.configure(services)
// Register hooks that run on all service methods
app.hooks({
around: {
all: [logError]
},
before: {},
after: {},
error: {}
})
// Register application setup and teardown hooks here
app.hooks({
setup: [],
teardown: []
})
export { app }

38
server/src/channels.ts Normal file
View File

@@ -0,0 +1,38 @@
// For more information about this file see https://dove.feathersjs.com/guides/cli/channels.html
import type { RealTimeConnection, Params } from '@feathersjs/feathers'
import type { AuthenticationResult } from '@feathersjs/authentication'
import '@feathersjs/transport-commons'
import type { Application, HookContext } from './declarations'
import { logger } from './logger'
export const channels = (app: Application) => {
logger.warn(
'Publishing all events to all authenticated users. See `channels.ts` and https://dove.feathersjs.com/api/channels.html for more information.'
)
app.on('connection', (connection: RealTimeConnection) => {
// On a new real-time connection, add it to the anonymous channel
app.channel('anonymous').join(connection)
})
app.on('login', (authResult: AuthenticationResult, { connection }: Params) => {
// connection can be undefined if there is no
// real-time connection, e.g. when logging in via REST
if (connection) {
// The connection is no longer anonymous, remove it
app.channel('anonymous').leave(connection)
// Add it to the authenticated user channel
app.channel('authenticated').join(connection)
}
})
// eslint-disable-next-line no-unused-vars
app.publish((data: any, context: HookContext) => {
// Here you can add event publishers to channels set up in `channels.js`
// To publish only for a specific event use `app.publish(eventname, () => {})`
// e.g. to publish all service events to all authenticated users use
return app.channel('authenticated')
})
}

34
server/src/client.ts Normal file
View File

@@ -0,0 +1,34 @@
// For more information about this file see https://dove.feathersjs.com/guides/cli/client.html
import { feathers } from '@feathersjs/feathers'
import type { TransportConnection, Application } from '@feathersjs/feathers'
import authenticationClient from '@feathersjs/authentication-client'
import type { AuthenticationClientOptions } from '@feathersjs/authentication-client'
export interface Configuration {
connection: TransportConnection<ServiceTypes>
}
export interface ServiceTypes {}
export type ClientApplication = Application<ServiceTypes, Configuration>
/**
* Returns a typed client for the server app.
*
* @param connection The REST or Socket.io Feathers client connection
* @param authenticationOptions Additional settings for the authentication client
* @see https://dove.feathersjs.com/api/client.html
* @returns The Feathers client application
*/
export const createClient = <Configuration = any>(
connection: TransportConnection<ServiceTypes>,
authenticationOptions: Partial<AuthenticationClientOptions> = {}
) => {
const client: ClientApplication = feathers()
client.configure(connection)
client.configure(authenticationClient(authenticationOptions))
client.set('connection', connection)
return client
}

View File

@@ -0,0 +1,17 @@
import { Type, getValidator, defaultAppConfiguration } from '@feathersjs/typebox'
import type { Static } from '@feathersjs/typebox'
import { dataValidator } from './validators'
export const configurationSchema = Type.Intersect([
defaultAppConfiguration,
Type.Object({
host: Type.String(),
port: Type.Number(),
public: Type.String()
})
])
export type ApplicationConfiguration = Static<typeof configurationSchema>
export const configurationValidator = getValidator(configurationSchema, dataValidator)

View File

@@ -0,0 +1,20 @@
// For more information about this file see https://dove.feathersjs.com/guides/cli/typescript.html
import { HookContext as FeathersHookContext, NextFunction } from '@feathersjs/feathers'
import { Application as FeathersApplication } from '@feathersjs/koa'
import { ApplicationConfiguration } from './configuration'
export { NextFunction }
// The types for app.get(name) and app.set(name)
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface Configuration extends ApplicationConfiguration {}
// A mapping of service names to types. Will be extended in service files.
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface ServiceTypes {}
// The application instance type that will be used everywhere else
export type Application = FeathersApplication<ServiceTypes, Configuration>
// The context for hook functions - can be typed with a service class
export type HookContext<S = any> = FeathersHookContext<Application, S>

View File

@@ -0,0 +1,18 @@
// For more information about this file see https://dove.feathersjs.com/guides/cli/log-error.html
import type { HookContext, NextFunction } from '../declarations'
import { logger } from '../logger'
export const logError = async (context: HookContext, next: NextFunction) => {
try {
await next()
} catch (error: any) {
logger.error(error.stack)
// Log validation errors
if (error.data) {
logger.error('Data: %O', error.data)
}
throw error
}
}

11
server/src/index.ts Normal file
View File

@@ -0,0 +1,11 @@
import { app } from './app'
import { logger } from './logger'
const port = app.get('port')
const host = app.get('host')
process.on('unhandledRejection', (reason) => logger.error('Unhandled Rejection %O', reason))
app.listen(port).then(() => {
logger.info(`Feathers app listening on http://${host}:${port}`)
})

10
server/src/logger.ts Normal file
View File

@@ -0,0 +1,10 @@
// For more information about this file see https://dove.feathersjs.com/guides/cli/logging.html
import { createLogger, format, transports } from 'winston'
// Configure the Winston logger. For the complete documentation see https://github.com/winstonjs/winston
export const logger = createLogger({
// To see more detailed errors, change this to 'debug'
level: 'info',
format: format.combine(format.splat(), format.simple()),
transports: [new transports.Console()]
})

View File

@@ -0,0 +1,6 @@
// For more information about this file see https://dove.feathersjs.com/guides/cli/application.html#configure-functions
import type { Application } from '../declarations'
export const services = (app: Application) => {
// All services will be registered here
}

17
server/src/sqlite.ts Normal file
View File

@@ -0,0 +1,17 @@
// For more information about this file see https://dove.feathersjs.com/guides/cli/databases.html
import knex from 'knex'
import type { Knex } from 'knex'
import type { Application } from './declarations'
declare module './declarations' {
interface Configuration {
sqliteClient: Knex
}
}
export const sqlite = (app: Application) => {
const config = app.get('sqlite')
const db = knex(config!)
app.set('sqliteClient', db)
}

29
server/src/validators.ts Normal file
View File

@@ -0,0 +1,29 @@
// For more information about this file see https://dove.feathersjs.com/guides/cli/validators.html
import { Ajv, addFormats } from '@feathersjs/schema'
import type { FormatsPluginOptions } from '@feathersjs/schema'
const formats: FormatsPluginOptions = [
'date-time',
'time',
'date',
'email',
'hostname',
'ipv4',
'ipv6',
'uri',
'uri-reference',
'uuid',
'uri-template',
'json-pointer',
'relative-json-pointer',
'regex'
]
export const dataValidator: Ajv = addFormats(new Ajv({}), formats)
export const queryValidator: Ajv = addFormats(
new Ajv({
coerceTypes: true
}),
formats
)

40
server/test/app.test.ts Normal file
View File

@@ -0,0 +1,40 @@
// For more information about this file see https://dove.feathersjs.com/guides/cli/app.test.html
import assert from 'assert'
import axios from 'axios'
import type { Server } from 'http'
import { app } from '../src/app'
const port = app.get('port')
const appUrl = `http://${app.get('host')}:${port}`
describe('Feathers application tests', () => {
let server: Server
before(async () => {
server = await app.listen(port)
})
after(async () => {
await app.teardown()
})
it('starts and shows the index page', async () => {
const { data } = await axios.get<string>(appUrl)
assert.ok(data.indexOf('<html lang="en">') !== -1)
})
it('shows a 404 JSON error', async () => {
try {
await axios.get(`${appUrl}/path/to/nowhere`, {
responseType: 'json'
})
assert.fail('should never get here')
} catch (error: any) {
const { response } = error
assert.strictEqual(response?.status, 404)
assert.strictEqual(response?.data?.code, 404)
assert.strictEqual(response?.data?.name, 'NotFound')
}
})
})

View File

@@ -0,0 +1,18 @@
import assert from 'assert'
import axios from 'axios'
import type { Server } from 'http'
import { app } from '../src/app'
import { createClient } from '../src/client'
import rest from '@feathersjs/rest-client'
const port = app.get('port')
const appUrl = `http://${app.get('host')}:${port}`
describe('client tests', () => {
const client = createClient(rest(appUrl).axios(axios))
it('initialized the client', () => {
assert.ok(client)
})
})

21
server/tsconfig.json Normal file
View File

@@ -0,0 +1,21 @@
{
"ts-node": {
"files": true
},
"compilerOptions": {
"target": "es2020",
"module": "commonjs",
"outDir": "./lib",
"rootDir": "./src",
"declaration": true,
"strict": true,
"esModuleInterop": true,
"sourceMap": true
},
"include": [
"src"
],
"exclude": [
"test"
]
}

4
server/yarn.lock Normal file
View File

@@ -0,0 +1,4 @@
# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
# yarn lockfile v1