Skip to content
This repository has been archived by the owner on May 23, 2024. It is now read-only.

Commit

Permalink
refactor: code overhaul, async/sync fix, better validation, better dx…
Browse files Browse the repository at this point in the history
… & docs

* refactor: server start / config validation WIP

* refactor: serverStart & registerDefaultPaths

* chore: update acknowledgements

* refactor: async/sync fix & handle resource stop

* feat: validate registerResourcePath args

* refactor: path func result rework

* chore: update readme
  • Loading branch information
c-wide authored Apr 7, 2023
1 parent acbee78 commit 2cecad1
Show file tree
Hide file tree
Showing 9 changed files with 239 additions and 169 deletions.
79 changes: 55 additions & 24 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,50 +1,81 @@
# Wide API
<div align="center">

## Description
# 🌐 Wide API

A resource for FiveM that provides an API to execute functions on the server.
**A FiveM resource that provides an API to execute functions on the server.**

### Getting Started
</div>

1. Download & unpack the release files.
2. Place the folder in your servers resources folder.
3. Edit the config.json file if desired.
4. Start the resource.
## 📚 Table of Contents

Note: All paths are prefixed with the name of the resource that registered it. (/wide-api/ensure-resource/)
- [Getting Started](#-getting-started)
- [Register Your Own Paths](#-register-your-own-paths)
- [Examples](#examples)
- [Resource Restarter](#-resource-restarter)
- [Handling Resource Restarts](#-handling-resource-restarts)
- [Access Keys](#-access-keys)
- [Caveats](#-caveats)
- [Acknowledgements](#-acknowledgements)

### Register your own paths
## 🚀 Getting Started

- This resource exposes one export:
1. **Download** and unpack the release files.
2. **Place** the folder in your server's `resources` folder.
3. **Edit** the `config.json` file according to your preferences.
4. **Start** the resource.

## 🛠 Register Your Own Paths

This resource exposes a single export. Your route handler can return any data you want or a custom ApiResponse. See [Caveats](#-caveats) below.

```javascript
registerResourcePath(path: string, handler: (queryParams: Record<string, string>) => ApiResponse | unknown)
```
registerResourcePath(path: string, handler: (queryParams: Record<string, string>) => ApiResponse | void)

### Examples

- **Lua** (use the colon operator, not the dot operator)

```lua
exports["wide-api"]:registerResourcePath("yourPathName", function(queryParams)

end)
```

- **JavaScript** (queryParams type is `Record<string, string>`)

```javascript
globalThis.exports['wide-api'].registerResourcePath(
'yourPathName',
(queryParams) => {},
);
```

### Resource restarter
## 🔁 Resource Restarter

- To use the 'ensure-resource' path, make sure it is enabled in the config.json file.
- This path accepts one query parameter "resourceName". (/wide-api/ensure-resource?resourceName=baseevents/)
- If using this path the resource will require the permission to use the "ensure", "start", and "stop" commands.
- Enable the 'ensure-resource' path in the `config.json` file.
- This path accepts one query parameter: `resourceName` (e.g. `/wide-api/ensure-resource?resourceName=baseevents/`).
- If using this path, the resource will require permission to use the "ensure", "start", and "stop" commands.

```
add_ace resource.wide-api command.ensure allow
add_ace resource.wide-api command.start allow
add_ace resource.wide-api command.stop allow
```

### Handling resource restarts
## 🔄 Handling Resource Restarts

- When the API starts listening the 'wide-api:startServer' command is triggered.
- When creating your own paths you can listen for this event and re-register any paths you've created.
- When the API starts listening, the 'wide-api:startServer' command is triggered.
- To handle resource restarts, listen for this event and re-register any paths you've created.

### Access Keys
## 🔑 Access Keys

- Access keys should be added in the config.json file.
- Access keys should be added in the `config.json` file.
- If no access keys are provided, the server listens with unrestricted access.
- If you do provide access keys then you must provide an access key in the 'x-api-key' header.
- When access keys are specified, make sure to include an access key in the 'x-api-key' header for requests.
- Access keys should be added in the format: { description: string, key: string }

### Acknowledgements
## ⚠️ Caveats

- [AvarianKnight](https://github.com/AvarianKnight) for the idea and rough draft.
- All paths are prefixed with the name of the resource that registered it (e.g., /wide-api/ensure-resource/).
- If your route handler does not return an object/table that matches the ApiResponse type, the API assumes a response code of 200 and provides your returned data on the "data" key of the API response. Check the [ApiResponse type](https://github.com/c-wide/wide-api/blob/acbee784552da106dc45106b058cb9cffde6d95b/src/response.ts#L25) for more details.
46 changes: 46 additions & 0 deletions src/config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import jsonschema from 'jsonschema';
import configSchema from '../config.schema.json';
import type { LoggerLevels } from '~/logger';

export type ResourceConfig = {
server: {
port: number;
enableCors: boolean;
accessKeys: Array<{ description: string; key: string }>;
};
defaultPaths: {
'ensure-resource': boolean;
};
logger: {
level: LoggerLevels;
};
compareVersionOnStart: boolean;
};

export type ConfigValidationResponse =
| { status: 'success' }
| { status: 'error'; errors: Array<string> };

const resourceConfig: ResourceConfig = JSON.parse(
LoadResourceFile(GetCurrentResourceName(), 'config.json'),
);

export function validateConfig(): ConfigValidationResponse {
const validatorResponse = new jsonschema.Validator().validate(
resourceConfig,
configSchema,
);

if (!validatorResponse.valid) {
return {
status: 'error',
errors: validatorResponse.errors.map((error) => error.stack),
};
}

return { status: 'success' };
}

export function getConfig(): ResourceConfig {
return resourceConfig;
}
58 changes: 0 additions & 58 deletions src/getConfig.ts

This file was deleted.

25 changes: 15 additions & 10 deletions src/index.ts
Original file line number Diff line number Diff line change
@@ -1,24 +1,29 @@
import { getConfig, validateConfig } from '~/getConfig';
import { getConfig, validateConfig } from '~/config';
import { startServer } from '~/startServer';
import { createEnsureResourcePath } from '~/ensureResource';
import { compareResourceVersion } from '~/versionChecker';
import { logger, setLoggerMinLevel } from '~/logger';

on('onResourceStart', async (resourceName: string) => {
if (resourceName === GetCurrentResourceName()) {
if (!validateConfig()) return;
const validationResult = validateConfig();

const config = getConfig();
if (validationResult.status !== 'success') {
logger.fatal(
`Invalid config.json detected. Errors: [${validationResult.errors.join(
', ',
)}]`,
);

if (config.compareVersionOnStart) {
await compareResourceVersion();
return;
}

const pathArr: Array<() => void> = [];
const config = getConfig();

if (config.defaultPaths['ensure-resource']) {
pathArr.push(createEnsureResourcePath);
if (config.compareVersionOnStart) {
await compareResourceVersion();
}

startServer(config.server, pathArr);
setLoggerMinLevel(config.logger.level);
startServer();
}
});
29 changes: 19 additions & 10 deletions src/logger.ts
Original file line number Diff line number Diff line change
@@ -1,16 +1,25 @@
import { Logger } from 'tslog';
import { getConfig, LoggerLevel } from '~/getConfig';

const level = getConfig().logger.level;
export const LoggerLevel = {
Debug: 'debug',
Info: 'info',
Warn: 'warn',
Error: 'error',
} as const;

export type LoggerLevels = typeof LoggerLevel[keyof typeof LoggerLevel];

const LoggerLevelMap: Record<LoggerLevels, number> = {
debug: 2,
info: 3,
warn: 4,
error: 5,
};

export const logger = new Logger({
prettyLogTemplate: '[{{dateIsoStr}}] [{{logLevelName}}] - ',
minLevel:
level === LoggerLevel.Debug
? 2
: level === LoggerLevel.Info
? 3
: level === LoggerLevel.Warn
? 4
: 5,
});

export function setLoggerMinLevel(level: LoggerLevels) {
logger.settings.minLevel = LoggerLevelMap[level];
}
10 changes: 10 additions & 0 deletions src/registerDefaultPaths.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { getConfig } from '~/config';
import { createEnsureResourcePath } from '~/ensureResource';

export function registerDefaultPaths() {
const config = getConfig();

if (config.defaultPaths['ensure-resource']) {
createEnsureResourcePath();
}
}
Loading

0 comments on commit 2cecad1

Please sign in to comment.