- Add versioning to API endpoints. - Refactored $db and $repos as global variables. - Extracted routes into separate components with deferred registration. - Support for a different db URL for location data than the one used by the application. - Added sqlite and passport dependencies (passport will soon be used to handle authentication).
This commit is contained in:
parent
f349b2c5be
commit
533ebe960f
26 changed files with 1853 additions and 146 deletions
.env.example
frontend
package-lock.jsonpackage.jsonsrc
tsconfig.json
11
.env.example
11
.env.example
|
@ -8,12 +8,19 @@ BACKEND_ADDRESS=127.0.0.1
|
|||
# Listen port for the backend (default: 3000)
|
||||
BACKEND_PORT=3000
|
||||
|
||||
# Database URL (required)
|
||||
# Application database URL (required)
|
||||
DB_URL=postgres://user:password@host:port/dbname
|
||||
|
||||
# If the location data is stored on another db than the one used by the backend,
|
||||
# you can specify a different database URL here.
|
||||
# DB_LOCATION_URL=postgres://user:password@host:port/dbname
|
||||
|
||||
# Database dialect (default: inferred from the URL)
|
||||
# DB_DIALECT=postgres
|
||||
|
||||
# Location database dialect (default: inferred from the URL)
|
||||
# DB_LOCATION_DIALECT=postgres
|
||||
|
||||
# Name of the table that contains the location points (required)
|
||||
DB_LOCATION_TABLE=gpsdata
|
||||
|
||||
|
@ -55,4 +62,4 @@ DB_LOCATION__POSTAL_CODE=postal_code
|
|||
###
|
||||
|
||||
VITE_API_BASE_URL=http://localhost:${BACKEND_PORT}
|
||||
VITE_API_PATH=/api
|
||||
VITE_API_PATH=/api/v1
|
||||
|
|
|
@ -11,7 +11,7 @@ export default defineConfig((env) => {
|
|||
const serverURL = new URL(
|
||||
envars.VITE_API_SERVER_URL ?? 'http://localhost:3000'
|
||||
);
|
||||
const serverAPIPath = envars.VITE_API_PATH ?? '/api';
|
||||
const serverAPIPath = envars.VITE_API_PATH ?? '/api/v1';
|
||||
|
||||
return {
|
||||
envDir: envDir,
|
||||
|
|
1481
package-lock.json
generated
1481
package-lock.json
generated
File diff suppressed because it is too large
Load diff
|
@ -27,8 +27,10 @@
|
|||
"cors": "^2.8.5",
|
||||
"dotenv": "^16.4.7",
|
||||
"express": "^4.21.2",
|
||||
"passport": "^0.7.0",
|
||||
"pg": "^8.13.3",
|
||||
"sequelize": "^6.37.5"
|
||||
"sequelize": "^6.37.5",
|
||||
"sqlite3": "^5.1.7"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cors": "^2.8.17",
|
||||
|
|
112
src/db/Db.ts
112
src/db/Db.ts
|
@ -1,38 +1,58 @@
|
|||
import { Sequelize, DataTypes, Dialect } from 'sequelize';
|
||||
import { Sequelize, Dialect } from 'sequelize';
|
||||
|
||||
export class Db {
|
||||
import GPSData from './types/GPSData';
|
||||
|
||||
class Db {
|
||||
private readonly url: string;
|
||||
private readonly locationUrl: string;
|
||||
private readonly locationTable: string;
|
||||
public readonly locationTableColumns: object;
|
||||
public readonly locationTableColumns: Record<string, string>;
|
||||
private readonly dialect: Dialect;
|
||||
private readonly sequelize: Sequelize;
|
||||
private readonly locationDialect: Dialect;
|
||||
private readonly appDb: Sequelize;
|
||||
private readonly locationDb: Sequelize;
|
||||
|
||||
private static readonly envColumnPrefix = 'DB_LOCATION__';
|
||||
|
||||
private constructor(
|
||||
opts: {
|
||||
url: string,
|
||||
locationUrl: string,
|
||||
locationTable: string,
|
||||
locationTableColumns: object,
|
||||
locationTableColumns: Record<string, string>,
|
||||
dialect: Dialect,
|
||||
locationDialect: Dialect | null,
|
||||
}
|
||||
) {
|
||||
this.url = opts.url;
|
||||
this.locationUrl = opts.locationUrl;
|
||||
this.locationTable = opts.locationTable;
|
||||
this.locationTableColumns = opts.locationTableColumns
|
||||
this.dialect = opts.dialect as Dialect;
|
||||
this.locationDialect = (opts.locationDialect || this.dialect) as Dialect;
|
||||
|
||||
this.sequelize = new Sequelize(this.url, {
|
||||
this.appDb = new Sequelize(this.url, {
|
||||
dialect: this.dialect,
|
||||
logging: process.env.DEBUG === 'true' ? console.log : false
|
||||
});
|
||||
|
||||
if (this.locationUrl === this.url) {
|
||||
this.locationDb = this.appDb;
|
||||
} else {
|
||||
this.locationDb = new Sequelize(this.locationUrl, {
|
||||
dialect: this.locationDialect,
|
||||
logging: process.env.DEBUG === 'true' ? console.log : false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public static fromEnv(): Db {
|
||||
const opts: any = {}
|
||||
opts.url = process.env.DB_URL;
|
||||
opts.locationUrl = process.env.DB_LOCATION_URL || opts.url;
|
||||
opts.locationTable = process.env.DB_LOCATION_TABLE;
|
||||
opts.dialect = process.env.DB_DIALECT || opts.url.split(':')[0];
|
||||
opts.locationDialect = process.env.DB_LOCATION_DIALECT || opts.locationUrl.split(':')[0];
|
||||
|
||||
if (!opts.url?.length) {
|
||||
console.error('No DB_URL provided');
|
||||
|
@ -77,82 +97,16 @@ export class Db {
|
|||
return `${Db.envColumnPrefix}${name.toUpperCase()}`;
|
||||
}
|
||||
|
||||
public GpsData() {
|
||||
const typeDef: any = {};
|
||||
/**
|
||||
* Tables
|
||||
*/
|
||||
|
||||
// @ts-expect-error
|
||||
typeDef[this.locationTableColumns['id']] = {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
};
|
||||
|
||||
// @ts-expect-error
|
||||
typeDef[this.locationTableColumns['latitude']] = {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: false
|
||||
};
|
||||
|
||||
// @ts-expect-error
|
||||
typeDef[this.locationTableColumns['longitude']] = {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: false
|
||||
};
|
||||
|
||||
// @ts-expect-error
|
||||
const altitudeCol: string = this.locationTableColumns['altitude'];
|
||||
if (altitudeCol?.length) {
|
||||
typeDef[altitudeCol] = {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: true
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
const addressCol: string = this.locationTableColumns['address'];
|
||||
if (addressCol?.length) {
|
||||
typeDef[addressCol] = {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
const localityCol: string = this.locationTableColumns['locality'];
|
||||
if (localityCol?.length) {
|
||||
typeDef[localityCol] = {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
const countryCol: string = this.locationTableColumns['country'];
|
||||
if (countryCol?.length) {
|
||||
typeDef[countryCol] = {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
const postalCodeCol: string = this.locationTableColumns['postal_code'];
|
||||
if (postalCodeCol?.length) {
|
||||
typeDef[postalCodeCol] = {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
};
|
||||
}
|
||||
|
||||
// @ts-expect-error
|
||||
typeDef[this.locationTableColumns['timestamp']] = {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW
|
||||
};
|
||||
|
||||
return this.sequelize.define('GpsData', typeDef, {
|
||||
public GPSData() {
|
||||
return this.locationDb.define('GPSData', GPSData(this.locationTableColumns), {
|
||||
tableName: this.locationTable,
|
||||
timestamps: false
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Db;
|
||||
|
|
3
src/db/index.ts
Normal file
3
src/db/index.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
import Db from "./Db";
|
||||
|
||||
export { Db };
|
70
src/db/types/GPSData.ts
Normal file
70
src/db/types/GPSData.ts
Normal file
|
@ -0,0 +1,70 @@
|
|||
import { DataTypes } from 'sequelize';
|
||||
|
||||
function GPSData(locationTableColumns: Record<string, string>): Record<string, any> {
|
||||
const typeDef: Record<string, any> = {};
|
||||
|
||||
typeDef[locationTableColumns['id']] = {
|
||||
type: DataTypes.INTEGER,
|
||||
primaryKey: true,
|
||||
autoIncrement: true
|
||||
};
|
||||
|
||||
typeDef[locationTableColumns['latitude']] = {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: false
|
||||
};
|
||||
|
||||
typeDef[locationTableColumns['longitude']] = {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: false
|
||||
};
|
||||
|
||||
const altitudeCol: string = locationTableColumns['altitude'];
|
||||
if (altitudeCol?.length) {
|
||||
typeDef[altitudeCol] = {
|
||||
type: DataTypes.FLOAT,
|
||||
allowNull: true
|
||||
};
|
||||
}
|
||||
|
||||
const addressCol: string = locationTableColumns['address'];
|
||||
if (addressCol?.length) {
|
||||
typeDef[addressCol] = {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
};
|
||||
}
|
||||
|
||||
const localityCol: string = locationTableColumns['locality'];
|
||||
if (localityCol?.length) {
|
||||
typeDef[localityCol] = {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
};
|
||||
}
|
||||
|
||||
const countryCol: string = locationTableColumns['country'];
|
||||
if (countryCol?.length) {
|
||||
typeDef[countryCol] = {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
};
|
||||
}
|
||||
|
||||
const postalCodeCol: string = locationTableColumns['postal_code'];
|
||||
if (postalCodeCol?.length) {
|
||||
typeDef[postalCodeCol] = {
|
||||
type: DataTypes.STRING,
|
||||
allowNull: true
|
||||
};
|
||||
}
|
||||
|
||||
typeDef[locationTableColumns['timestamp']] = {
|
||||
type: DataTypes.DATE,
|
||||
defaultValue: DataTypes.NOW
|
||||
};
|
||||
|
||||
return typeDef;
|
||||
}
|
||||
|
||||
export default GPSData;
|
16
src/globals.ts
Normal file
16
src/globals.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import dotenv from 'dotenv';
|
||||
|
||||
import { Db } from './db';
|
||||
import Repositories from './repos';
|
||||
|
||||
dotenv.config();
|
||||
|
||||
declare global {
|
||||
var $db: Db;
|
||||
var $repos: Repositories;
|
||||
}
|
||||
|
||||
export function useGlobals() {
|
||||
globalThis.$db = Db.fromEnv();
|
||||
globalThis.$repos = new Repositories(globalThis.$db);
|
||||
}
|
41
src/main.ts
41
src/main.ts
|
@ -1,49 +1,20 @@
|
|||
import cors from 'cors';
|
||||
import express from 'express';
|
||||
import dotenv from 'dotenv';
|
||||
|
||||
import { Db } from './db/Db';
|
||||
import { LocationRequest } from './models/LocationRequest';
|
||||
import { LocationRepository } from './repo/LocationRepository';
|
||||
import { logRequest } from './helpers/logging';
|
||||
import { useGlobals } from './globals';
|
||||
useGlobals();
|
||||
|
||||
dotenv.config();
|
||||
import Routes from './routes';
|
||||
|
||||
const app = express();
|
||||
const db = Db.fromEnv();
|
||||
const locationRepo = new LocationRepository(db);
|
||||
const address = process.env.BACKEND_ADDRESS || '127.0.0.1';
|
||||
const port = process.env.BACKEND_PORT || 3000;
|
||||
const routes = new Routes();
|
||||
|
||||
// Middleware
|
||||
app.use(cors());
|
||||
app.use(express.static('frontend/dist'));
|
||||
routes.register(app)
|
||||
|
||||
// API route
|
||||
app.get('/api/gpsdata', async (req, res) => {
|
||||
logRequest(req);
|
||||
let query: any = {};
|
||||
|
||||
try {
|
||||
query = new LocationRequest(req.query);
|
||||
} catch (error) {
|
||||
const e = `Error parsing query: ${error}`;
|
||||
console.warn(e);
|
||||
res.status(400).send(e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const gpsData = await locationRepo.getHistory(query);
|
||||
res.json(gpsData);
|
||||
} catch (error) {
|
||||
const e = `Error fetching data: ${error}`;
|
||||
console.error(e);
|
||||
res.status(500).send(e);
|
||||
}
|
||||
});
|
||||
|
||||
// @ts-ignore
|
||||
app.listen(port, address, () => {
|
||||
app.listen(new Number(port).valueOf(), address, () => {
|
||||
console.log(`Server is running on port ${address}:${port}`);
|
||||
});
|
||||
|
|
|
@ -22,4 +22,4 @@ class GPSPoint {
|
|||
}
|
||||
}
|
||||
|
||||
export { GPSPoint };
|
||||
export default GPSPoint;
|
||||
|
|
|
@ -1,3 +0,0 @@
|
|||
type Nullable<T> = T | null;
|
||||
|
||||
export { Nullable };
|
5
src/models/index.ts
Normal file
5
src/models/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import GPSPoint from "./GPSPoint";
|
||||
|
||||
export {
|
||||
GPSPoint,
|
||||
};
|
|
@ -1,8 +1,8 @@
|
|||
import { Db } from '../db/Db';
|
||||
import { GPSPoint } from '../models/GPSPoint';
|
||||
import { LocationRequest } from 'src/models/LocationRequest';
|
||||
import { Db } from '~/db';
|
||||
import { GPSPoint } from '../models';
|
||||
import { LocationRequest } from '../requests';
|
||||
|
||||
export class LocationRepository {
|
||||
class LocationRepository {
|
||||
private db: Db;
|
||||
|
||||
constructor(db: Db) {
|
||||
|
@ -13,7 +13,7 @@ export class LocationRepository {
|
|||
let apiResponse: any[] = [];
|
||||
|
||||
try {
|
||||
apiResponse = await this.db.GpsData().findAll(query.toMap(this.db));
|
||||
apiResponse = await this.db.GPSData().findAll(query.toMap(this.db));
|
||||
} catch (error) {
|
||||
throw new Error(`Error fetching data: ${error}`);
|
||||
}
|
||||
|
@ -40,3 +40,5 @@ export class LocationRepository {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default LocationRepository;
|
12
src/repos/index.ts
Normal file
12
src/repos/index.ts
Normal file
|
@ -0,0 +1,12 @@
|
|||
import { Db } from 'src/db';
|
||||
import LocationRepository from './LocationRepository';
|
||||
|
||||
class Repositories {
|
||||
public location: LocationRepository;
|
||||
|
||||
constructor(db: Db) {
|
||||
this.location = new LocationRepository(db);
|
||||
}
|
||||
}
|
||||
|
||||
export default Repositories;
|
|
@ -1,17 +1,18 @@
|
|||
import { Nullable } from './Types';
|
||||
import { Db } from '../db/Db';
|
||||
import { Op } from 'sequelize';
|
||||
|
||||
import { Optional } from 'src/types';
|
||||
import { Db } from 'src/db';
|
||||
|
||||
class LocationRequest {
|
||||
limit: Nullable<number> = 250;
|
||||
offset: Nullable<number> = null;
|
||||
startDate: Nullable<Date> = null;
|
||||
endDate: Nullable<Date> = null;
|
||||
minId: Nullable<number> = null;
|
||||
maxId: Nullable<number> = null;
|
||||
country: Nullable<string> = null;
|
||||
locality: Nullable<string> = null;
|
||||
postalCode: Nullable<string> = null;
|
||||
limit: Optional<number> = 250;
|
||||
offset: Optional<number> = null;
|
||||
startDate: Optional<Date> = null;
|
||||
endDate: Optional<Date> = null;
|
||||
minId: Optional<number> = null;
|
||||
maxId: Optional<number> = null;
|
||||
country: Optional<string> = null;
|
||||
locality: Optional<string> = null;
|
||||
postalCode: Optional<string> = null;
|
||||
orderBy: string = 'timestamp';
|
||||
order: string = 'DESC';
|
||||
|
||||
|
@ -99,4 +100,4 @@ class LocationRequest {
|
|||
}
|
||||
}
|
||||
|
||||
export { LocationRequest };
|
||||
export default LocationRequest;
|
5
src/requests/index.ts
Normal file
5
src/requests/index.ts
Normal file
|
@ -0,0 +1,5 @@
|
|||
import LocationRequest from "./LocationRequest";
|
||||
|
||||
export {
|
||||
LocationRequest,
|
||||
}
|
75
src/routes/Route.ts
Normal file
75
src/routes/Route.ts
Normal file
|
@ -0,0 +1,75 @@
|
|||
import { Express, Request, Response } from 'express';
|
||||
|
||||
import { logRequest } from '../helpers/logging';
|
||||
|
||||
abstract class Route {
|
||||
protected readonly path: string;
|
||||
|
||||
constructor(path: string) {
|
||||
this.path = path;
|
||||
}
|
||||
|
||||
protected static NotAllowed(res: Response) {
|
||||
res.status(405).send('Method Not Allowed');
|
||||
}
|
||||
|
||||
private getRoute: (req: Request, res: Response) => Promise<void> = async (req, res) => {
|
||||
logRequest(req);
|
||||
return await this.get(req, res);
|
||||
}
|
||||
|
||||
private postRoute: (req: Request, res: Response) => Promise<void> = async (req, res) => {
|
||||
logRequest(req);
|
||||
return await this.post(req, res);
|
||||
}
|
||||
|
||||
private putRoute: (req: Request, res: Response) => Promise<void> = async (req, res) => {
|
||||
logRequest(req);
|
||||
return await this.put(req, res);
|
||||
}
|
||||
|
||||
private deleteRoute: (req: Request, res: Response) => Promise<void> = async (req, res) => {
|
||||
logRequest(req);
|
||||
return await this.delete(req, res);
|
||||
}
|
||||
|
||||
private patchRoute: (req: Request, res: Response) => Promise<void> = async (req, res) => {
|
||||
logRequest(req);
|
||||
return await this.patch(req, res);
|
||||
}
|
||||
|
||||
public get: (req: Request, res: Response) => Promise<void> = async (_, res) => {
|
||||
Route.NotAllowed(res);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public post: (req: Request, res: Response) => Promise<void> = async (_, res) => {
|
||||
Route.NotAllowed(res);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public put: (req: Request, res: Response) => Promise<void> = async (_, res) => {
|
||||
Route.NotAllowed(res);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public delete: (req: Request, res: Response) => Promise<void> = async (_, res) => {
|
||||
Route.NotAllowed(res);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public patch: (req: Request, res: Response) => Promise<void> = async (_, res) => {
|
||||
Route.NotAllowed(res);
|
||||
return Promise.resolve();
|
||||
}
|
||||
|
||||
public register(app: Express) {
|
||||
app.get(this.path, this.getRoute);
|
||||
app.post(this.path, this.postRoute);
|
||||
app.put(this.path, this.putRoute);
|
||||
app.delete(this.path, this.deleteRoute);
|
||||
app.patch(this.path, this.patchRoute);
|
||||
}
|
||||
}
|
||||
|
||||
export default Route;
|
15
src/routes/Routes.ts
Normal file
15
src/routes/Routes.ts
Normal file
|
@ -0,0 +1,15 @@
|
|||
import { Express } from 'express';
|
||||
|
||||
import Route from './Route';
|
||||
|
||||
abstract class Routes {
|
||||
public abstract routes: Route[];
|
||||
|
||||
public register(app: Express) {
|
||||
this.routes.forEach((route) => {
|
||||
route.register(app);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Routes;
|
19
src/routes/api/Route.ts
Normal file
19
src/routes/api/Route.ts
Normal file
|
@ -0,0 +1,19 @@
|
|||
import Route from '../Route';
|
||||
|
||||
abstract class ApiRoute extends Route {
|
||||
protected version: string;
|
||||
|
||||
constructor(path: string, version: string) {
|
||||
super(ApiRoute.toApiPath(path, version));
|
||||
}
|
||||
|
||||
protected static toApiPath(path: string, version: string): string {
|
||||
if (!path.startsWith('/')) {
|
||||
path = `/${path}`;
|
||||
}
|
||||
|
||||
return `/api/${version}${path}`;
|
||||
}
|
||||
}
|
||||
|
||||
export default ApiRoute;
|
9
src/routes/api/index.ts
Normal file
9
src/routes/api/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import ApiV1Routes from "./v1";
|
||||
import Routes from "../Routes";
|
||||
|
||||
class ApiRoutes extends Routes {
|
||||
private v1: ApiV1Routes = new ApiV1Routes();
|
||||
public routes = [...this.v1.routes];
|
||||
}
|
||||
|
||||
export default ApiRoutes;
|
37
src/routes/api/v1/GPSData.ts
Normal file
37
src/routes/api/v1/GPSData.ts
Normal file
|
@ -0,0 +1,37 @@
|
|||
import { Request, Response } from 'express';
|
||||
|
||||
import { LocationRequest } from '../../../requests';
|
||||
import LocationRepository from '~/repos/LocationRepository';
|
||||
import ApiV1Route from './Route';
|
||||
|
||||
const $location: LocationRepository = globalThis.$repos.location;
|
||||
|
||||
class GPSData extends ApiV1Route {
|
||||
constructor() {
|
||||
super('/gpsdata');
|
||||
}
|
||||
|
||||
get = async (req: Request, res: Response) => {
|
||||
let query: LocationRequest
|
||||
|
||||
try {
|
||||
query = new LocationRequest(req.query);
|
||||
} catch (error) {
|
||||
const e = `Error parsing query: ${error}`;
|
||||
console.warn(e);
|
||||
res.status(400).send(e);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const gpsData = await $location.getHistory(query);
|
||||
res.json(gpsData);
|
||||
} catch (error) {
|
||||
const e = `Error fetching data: ${error}`;
|
||||
console.error(e);
|
||||
res.status(500).send(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default GPSData;
|
9
src/routes/api/v1/Route.ts
Normal file
9
src/routes/api/v1/Route.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import ApiRoute from '../Route';
|
||||
|
||||
abstract class ApiV1Route extends ApiRoute {
|
||||
constructor(path: string) {
|
||||
super(path, 'v1');
|
||||
}
|
||||
}
|
||||
|
||||
export default ApiV1Route;
|
8
src/routes/api/v1/index.ts
Normal file
8
src/routes/api/v1/index.ts
Normal file
|
@ -0,0 +1,8 @@
|
|||
import GPSData from "./GPSData";
|
||||
import Routes from "../../Routes";
|
||||
|
||||
class ApiV1Routes extends Routes {
|
||||
public routes = [new GPSData()];
|
||||
}
|
||||
|
||||
export default ApiV1Routes;
|
9
src/routes/index.ts
Normal file
9
src/routes/index.ts
Normal file
|
@ -0,0 +1,9 @@
|
|||
import ApiRoutes from "./api";
|
||||
import Routes from "./Routes";
|
||||
|
||||
class AllRoutes extends Routes {
|
||||
private api: ApiRoutes = new ApiRoutes();
|
||||
public routes = [...this.api.routes];
|
||||
}
|
||||
|
||||
export default AllRoutes;
|
3
src/types.ts
Normal file
3
src/types.ts
Normal file
|
@ -0,0 +1,3 @@
|
|||
type Optional<T> = T | null | undefined;
|
||||
|
||||
export { Optional };
|
|
@ -21,8 +21,15 @@
|
|||
"emitDecoratorMetadata": true,
|
||||
"experimentalDecorators": true,
|
||||
"resolveJsonModule": true,
|
||||
"baseUrl": "."
|
||||
"baseUrl": ".",
|
||||
"rootDir": "./src",
|
||||
"paths": {
|
||||
"~/*": ["./src/*"]
|
||||
}
|
||||
},
|
||||
"exclude": ["node_modules"],
|
||||
"include": ["./src/**/*.ts"]
|
||||
"include": [
|
||||
"./src/**/*.ts",
|
||||
"./src/**/*.d.ts"
|
||||
]
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue