Skip to content

Commit

Permalink
Merge pull request #49 from ItzNotABug/clean-urls
Browse files Browse the repository at this point in the history
Feat: Clean Urls
  • Loading branch information
ItzNotABug authored Oct 26, 2024
2 parents 72c5286 + 1aacde9 commit b8ff4d0
Show file tree
Hide file tree
Showing 5 changed files with 92 additions and 27 deletions.
29 changes: 27 additions & 2 deletions source/appexpress.js
Original file line number Diff line number Diff line change
Expand Up @@ -156,9 +156,12 @@ class AppExpress {
/** @type {{br: number, deflate: number, gzip: number}}*/
#compressionLevel = { br: 11, gzip: 6, deflate: 6 };

/** @type string[] */
#cleanUrlExtensions = [];

/**
* The base directory inside the docker container where the function is run.\
* See [here](https://github.com/open-runtimes/open-runtimes/blob/16bf063b60f1f2a150b6caa9afdd2d1786e7ca35/runtimes/node-18.0/src/server.js#L6) how the exact path is derived.
* See [here](https://github.com/open-runtimes/open-runtimes/blob/main/runtimes/node/versions/latest/src/server.js#L5) how the exact path is derived.
*
* @type string
*/
Expand Down Expand Up @@ -434,7 +437,15 @@ class AppExpress {
const filesMapping = this.#processDirectory(directory, exclude);

this.middleware((request, response) => {
const requestedFile = filesMapping[request.path];
let requestedFile = filesMapping[request.path];

// If `clean URLs` and no match found, check with `ext`.
if (!requestedFile && this.#cleanUrlExtensions.length) {
requestedFile = this.#cleanUrlExtensions
.map((ext) => `${request.path}.${ext}`)
.reduce((acc, path) => acc || filesMapping[path], null);
}

if (requestedFile) {
const options = {};
const contentType = mime.lookup(requestedFile) || defType;
Expand All @@ -458,6 +469,20 @@ class AppExpress {
}
}

/**
* Configure clean URLs for specific file extensions.\
* This feature is **disabled** by default.
*
* _Note: Only works for files added via {@link AppExpress#static} method._
*
* @example `index` > `index.html`, `contact` > `contact.html`
*
* @param {string[]} extensions - List of file extensions (e.g., ['html']) for clean URLs.
*/
cleanUrls(extensions = []) {
this.#cleanUrlExtensions = extensions;
}

/**
* Whether to add response header - `X-Powered-By: AppExpress`.\
* If a custom value for the header is provided, it will be preserved.
Expand Down
12 changes: 6 additions & 6 deletions source/package.json
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
{
"name": "@itznotabug/appexpress",
"version": "1.6.2",
"version": "1.6.3",
"description": "An `express.js` like framework for Appwrite Functions, enabling super-easy navigation!",
"author": "@itznotabug",
"type": "module",
"main": "appexpress.js",
"license": "Apache-2.0",
"scripts": {
"tests": "cd tests && node test.js",
"lint": "prettier . --check",
"format": "prettier . --write"
"format": "prettier . --write",
"tests": "cd tests && node test.js"
},
"prettier": {
"tabWidth": 4,
Expand All @@ -24,12 +24,12 @@
"mime-types": "2.1.35"
},
"devDependencies": {
"pug": "3.0.2",
"pug": "3.0.3",
"ejs": "3.1.10",
"showdown": "2.1.0",
"prettier": "3.2.5",
"prettier": "3.3.3",
"express-hbs": "2.5.0",
"@itznotabug/appexpress-nocookies": "^0.0.2"
"@itznotabug/appexpress-nocookies": "^0.0.4"
},
"keywords": [
"appwrite",
Expand Down
39 changes: 20 additions & 19 deletions source/tests/src/function/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const repositoryOne = new LoremIpsumRepository();
const repositoryTwo = new LoremIpsumRepository();

express.views('views');
express.cleanUrls(['html', 'txt']);
express.static('public', [/^\..*env.*/i]);

express.engine('ejs', ejs); // ejs
Expand Down Expand Up @@ -78,7 +79,7 @@ express.inject(repositoryTwo, crypto.randomUUID().replace(/-/g, ''));

// middleware for auth
express.middleware((request) => {
const { userJwtToken } = request.body;
const { userJwtToken } = request.bodyJson;
const isConsole = request.path.includes('/console');

if (isConsole && !userJwtToken) {
Expand All @@ -92,7 +93,7 @@ express.middleware((request, response) => {
request.method === 'get' && request.path.includes('assets');
if (isFavIconPath) {
const { mode } = request.query;
response.send(
response.text(
`we don't really have a ${mode ? 'dark ' : ''}favicon yet, sorry`,
404,
);
Expand All @@ -107,7 +108,7 @@ express.middleware({
});

// hard cookie remover
express.middleware(noCookies.middleware);
express.middleware(noCookies());

// override body content
express.middleware({
Expand All @@ -117,18 +118,18 @@ express.middleware({
});

// directs
express.get('/', (request, response) => response.send(request.method));
express.post('/', (request, response) => response.send(request.method));
express.put('/', (request, response) => response.send(request.method));
express.patch('/', (request, response) => response.send(request.method));
express.delete('/', (request, response) => response.send(request.method));
express.options('/', (request, response) => response.send(request.method));
express.get('/', (request, response) => response.text(request.method));
express.post('/', (request, response) => response.text(request.method));
express.put('/', (request, response) => response.text(request.method));
express.patch('/', (request, response) => response.text(request.method));
express.delete('/', (request, response) => response.text(request.method));
express.options('/', (request, response) => response.text(request.method));

// with router
router.get('/empty', (request, response) => response.empty());
router.get('/', (request, response) => response.json(request.body));
router.get('/', (request, response) => response.json(request.bodyJson));
router.post('/:user', (request, response) =>
response.send(request.params.user),
response.text(request.params.user),
);
router.post('/:user/:transaction', (request, response) => {
response.json({
Expand All @@ -146,21 +147,21 @@ express.get('/get', (_, __) => {
});

// all
express.all('/all', (_, response) => response.send('same on all'));
express.all('/all', (_, response) => response.text('same on all'));

// auth with a hooked middleware
express.get('/console', (_, response) => response.send('console'));
express.get('/console', (_, response) => response.text('console'));

// use injected repo for test
const injectionRouter = new AppExpress.Router();
injectionRouter.get('/', (request, response) => {
const repository = request.retrieve(LoremIpsumRepository, 'one');
response.send(repository.get());
response.text(repository.get());
});

injectionRouter.get('/error', (request, response) => {
const repository = request.retrieve(LoremIpsumRepository);
response.send(repository.get());
response.text(repository.get());
});

express.use('/lorem_ipsum', injectionRouter);
Expand All @@ -170,10 +171,10 @@ const headersRouter = new AppExpress.Router();
headersRouter.get('/:uuid', (request, response) => {
const { uuid } = request.params;
response.setHeaders({ 'custom-header': uuid });
response.send(uuid);
response.text(uuid);
});

headersRouter.get('/clear', (_, response) => response.send('cleared'));
headersRouter.get('/clear', (_, response) => response.text('cleared'));
express.use('/headers', headersRouter);

express.get('/engines/:extension', (request, response) => {
Expand All @@ -195,8 +196,8 @@ express.get('/engines/article', (request, response) => {

// multiple returns are not allowed.
express.get('/error/multi-return', (_, response) => {
response.send('ok');
response.send('ok');
response.text('ok');
response.text('ok');
});

express.get('/outgoing', (_, res) => res.empty());
Expand Down
1 change: 1 addition & 0 deletions source/tests/src/function/public/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
<h1>Hello, World</h1>
38 changes: 38 additions & 0 deletions source/tests/test.js
Original file line number Diff line number Diff line change
Expand Up @@ -441,3 +441,41 @@ describe('Extended middleware validation', () => {
assert.strictEqual(body, 'outgoing');
});
});

describe('Clean URLs validation', () => {
it(`should return index.html content on requesting just index path`, async () => {
const indexHtml = `${publicDir}/index.html`;
const indexContent = fs.readFileSync(indexHtml, 'utf8');

const context = createContext({
path: '/index',
});

const { body } = await index(context);
assert.strictEqual(body, indexContent);
});

it(`should return robots.txt content on requesting just the robots path`, async () => {
const robotsFile = `${publicDir}/robots.txt`;
const robotsFileContent = fs.readFileSync(robotsFile, 'utf8');

const context = createContext({
path: '/robots',
});

const { body } = await index(context);
assert.strictEqual(body, robotsFileContent);
});

it(`should return ads.txt content on requesting just the ads path`, async () => {
const robotsFile = `${publicDir}/ads.txt`;
const robotsFileContent = fs.readFileSync(robotsFile, 'utf8');

const context = createContext({
path: '/ads',
});

const { body } = await index(context);
assert.strictEqual(body, robotsFileContent);
});
});

0 comments on commit b8ff4d0

Please sign in to comment.