From 0040dc63840462cf73a152a0b0428c342c7a32c9 Mon Sep 17 00:00:00 2001 From: Elias Renman Date: Sat, 20 May 2023 09:18:45 +0200 Subject: [PATCH] chore: setup featherjs project --- server/.gitignore | 121 ++++++++++++++++++ server/.npmignore | 121 ++++++++++++++++++ server/.prettierrc | 9 ++ .../config/custom-environment-variables.json | 10 ++ server/config/default.json | 17 +++ server/config/test.json | 3 + server/knexfile.ts | 7 + server/node_modules/.yarn-integrity | 10 ++ server/package.json | 48 +++++++ server/public/index.html | 37 ++++++ server/readme.md | 42 ++++++ server/src/app.ts | 54 ++++++++ server/src/channels.ts | 38 ++++++ server/src/client.ts | 34 +++++ server/src/configuration.ts | 17 +++ server/src/declarations.ts | 20 +++ server/src/hooks/log-error.ts | 18 +++ server/src/index.ts | 11 ++ server/src/logger.ts | 10 ++ server/src/services/index.ts | 6 + server/src/sqlite.ts | 17 +++ server/src/validators.ts | 29 +++++ server/test/app.test.ts | 40 ++++++ server/test/client.test.ts | 18 +++ server/tsconfig.json | 21 +++ server/yarn.lock | 4 + 26 files changed, 762 insertions(+) create mode 100644 server/.gitignore create mode 100644 server/.npmignore create mode 100644 server/.prettierrc create mode 100644 server/config/custom-environment-variables.json create mode 100644 server/config/default.json create mode 100644 server/config/test.json create mode 100644 server/knexfile.ts create mode 100644 server/node_modules/.yarn-integrity create mode 100644 server/package.json create mode 100644 server/public/index.html create mode 100644 server/readme.md create mode 100644 server/src/app.ts create mode 100644 server/src/channels.ts create mode 100644 server/src/client.ts create mode 100644 server/src/configuration.ts create mode 100644 server/src/declarations.ts create mode 100644 server/src/hooks/log-error.ts create mode 100644 server/src/index.ts create mode 100644 server/src/logger.ts create mode 100644 server/src/services/index.ts create mode 100644 server/src/sqlite.ts create mode 100644 server/src/validators.ts create mode 100644 server/test/app.test.ts create mode 100644 server/test/client.test.ts create mode 100644 server/tsconfig.json create mode 100644 server/yarn.lock diff --git a/server/.gitignore b/server/.gitignore new file mode 100644 index 0000000..82cf9aa --- /dev/null +++ b/server/.gitignore @@ -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/ diff --git a/server/.npmignore b/server/.npmignore new file mode 100644 index 0000000..82cf9aa --- /dev/null +++ b/server/.npmignore @@ -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/ diff --git a/server/.prettierrc b/server/.prettierrc new file mode 100644 index 0000000..2df6504 --- /dev/null +++ b/server/.prettierrc @@ -0,0 +1,9 @@ +{ + "tabWidth": 2, + "useTabs": false, + "printWidth": 110, + "semi": false, + "trailingComma": "none", + "singleQuote": true, + "parser": "typescript" +} \ No newline at end of file diff --git a/server/config/custom-environment-variables.json b/server/config/custom-environment-variables.json new file mode 100644 index 0000000..283387f --- /dev/null +++ b/server/config/custom-environment-variables.json @@ -0,0 +1,10 @@ +{ + "port": { + "__name": "PORT", + "__format": "number" + }, + "host": "HOSTNAME", + "authentication": { + "secret": "FEATHERS_SECRET" + } +} \ No newline at end of file diff --git a/server/config/default.json b/server/config/default.json new file mode 100644 index 0000000..2da2ca8 --- /dev/null +++ b/server/config/default.json @@ -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 + } +} \ No newline at end of file diff --git a/server/config/test.json b/server/config/test.json new file mode 100644 index 0000000..ab43666 --- /dev/null +++ b/server/config/test.json @@ -0,0 +1,3 @@ +{ + "port": 8998 +} \ No newline at end of file diff --git a/server/knexfile.ts b/server/knexfile.ts new file mode 100644 index 0000000..9b789de --- /dev/null +++ b/server/knexfile.ts @@ -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 diff --git a/server/node_modules/.yarn-integrity b/server/node_modules/.yarn-integrity new file mode 100644 index 0000000..ae93109 --- /dev/null +++ b/server/node_modules/.yarn-integrity @@ -0,0 +1,10 @@ +{ + "systemParams": "linux-x64-108", + "modulesFolders": [], + "flags": [], + "linkedModules": [], + "topLevelPatterns": [], + "lockfileEntries": {}, + "files": [], + "artifacts": {} +} \ No newline at end of file diff --git a/server/package.json b/server/package.json new file mode 100644 index 0000000..157d59f --- /dev/null +++ b/server/package.json @@ -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" + } +} \ No newline at end of file diff --git a/server/public/index.html b/server/public/index.html new file mode 100644 index 0000000..5a57b63 --- /dev/null +++ b/server/public/index.html @@ -0,0 +1,37 @@ + + + + server + + + + + + + + + diff --git a/server/readme.md b/server/readme.md new file mode 100644 index 0000000..0ba3ed6 --- /dev/null +++ b/server/readme.md @@ -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). diff --git a/server/src/app.ts b/server/src/app.ts new file mode 100644 index 0000000..cf8129b --- /dev/null +++ b/server/src/app.ts @@ -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 } diff --git a/server/src/channels.ts b/server/src/channels.ts new file mode 100644 index 0000000..9c72f46 --- /dev/null +++ b/server/src/channels.ts @@ -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') + }) +} diff --git a/server/src/client.ts b/server/src/client.ts new file mode 100644 index 0000000..ad1286d --- /dev/null +++ b/server/src/client.ts @@ -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 +} + +export interface ServiceTypes {} + +export type ClientApplication = Application + +/** + * 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 = ( + connection: TransportConnection, + authenticationOptions: Partial = {} +) => { + const client: ClientApplication = feathers() + + client.configure(connection) + client.configure(authenticationClient(authenticationOptions)) + client.set('connection', connection) + + return client +} diff --git a/server/src/configuration.ts b/server/src/configuration.ts new file mode 100644 index 0000000..44eaa0c --- /dev/null +++ b/server/src/configuration.ts @@ -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 + +export const configurationValidator = getValidator(configurationSchema, dataValidator) diff --git a/server/src/declarations.ts b/server/src/declarations.ts new file mode 100644 index 0000000..b35505e --- /dev/null +++ b/server/src/declarations.ts @@ -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 + +// The context for hook functions - can be typed with a service class +export type HookContext = FeathersHookContext diff --git a/server/src/hooks/log-error.ts b/server/src/hooks/log-error.ts new file mode 100644 index 0000000..4010617 --- /dev/null +++ b/server/src/hooks/log-error.ts @@ -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 + } +} diff --git a/server/src/index.ts b/server/src/index.ts new file mode 100644 index 0000000..dc4ec62 --- /dev/null +++ b/server/src/index.ts @@ -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}`) +}) diff --git a/server/src/logger.ts b/server/src/logger.ts new file mode 100644 index 0000000..3280e9e --- /dev/null +++ b/server/src/logger.ts @@ -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()] +}) diff --git a/server/src/services/index.ts b/server/src/services/index.ts new file mode 100644 index 0000000..c32063b --- /dev/null +++ b/server/src/services/index.ts @@ -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 +} diff --git a/server/src/sqlite.ts b/server/src/sqlite.ts new file mode 100644 index 0000000..86b405a --- /dev/null +++ b/server/src/sqlite.ts @@ -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) +} diff --git a/server/src/validators.ts b/server/src/validators.ts new file mode 100644 index 0000000..015c65f --- /dev/null +++ b/server/src/validators.ts @@ -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 +) diff --git a/server/test/app.test.ts b/server/test/app.test.ts new file mode 100644 index 0000000..0ac9848 --- /dev/null +++ b/server/test/app.test.ts @@ -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(appUrl) + + assert.ok(data.indexOf('') !== -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') + } + }) +}) diff --git a/server/test/client.test.ts b/server/test/client.test.ts new file mode 100644 index 0000000..1748a9d --- /dev/null +++ b/server/test/client.test.ts @@ -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) + }) +}) diff --git a/server/tsconfig.json b/server/tsconfig.json new file mode 100644 index 0000000..0277dfb --- /dev/null +++ b/server/tsconfig.json @@ -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" + ] +} \ No newline at end of file diff --git a/server/yarn.lock b/server/yarn.lock new file mode 100644 index 0000000..fb57ccd --- /dev/null +++ b/server/yarn.lock @@ -0,0 +1,4 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + +