More or less finalized the REST api.

This commit is contained in:
didier amyot committed 2023-06-13 11:42:59 -04:00
1 parent 872f841d74
commit 95e58a67df
5 files changed
+159 -7

No files matched your search

+45 -6
View File
@@ -1,9 +1,10 @@
import { Context, Router } from './deps.ts'; import { Context, Router, TextLineStream, readableStreamFromReader } from './deps.ts';
import { FileMetaData, FileService } from './services/mod.ts'; import { FileMetaData, FileService } from './services/mod.ts';
import { readerFromStreamReader } from './deps.ts'; import { readerFromStreamReader } from './deps.ts';
import { asyncIterableToArray } from './utils.ts'; import { asyncIterableToArray } from './utils.ts';
import { createMockNext } from '../vendor/deno.land/x/oak@v12.1.0/testing.ts';
import { Status } from "../vendor/deno.land/std@0.178.0/http/http_status.ts"; import { Status } from "../vendor/deno.land/std@0.178.0/http/http_status.ts";
import { WaveFrontServiceImpl } from "./services/wavefrontService.ts";
import { Vector3D, WaveFrontService } from './services/interfaces.ts';
type Handler = (ctx: Context) => void; type Handler = (ctx: Context) => void;
@@ -11,6 +12,7 @@ class FileDTO {
readonly path: string; readonly path: string;
readonly id: string; readonly id: string;
readonly contentSize: number; readonly contentSize: number;
readonly fileInfoURL: string;
readonly contentURL: string; readonly contentURL: string;
constructor(path: string, id: string, size: number){ constructor(path: string, id: string, size: number){
@@ -18,6 +20,7 @@ class FileDTO {
this.id = id; this.id = id;
this.contentSize = size; this.contentSize = size;
this.contentURL = `/v1/content/${id}/raw`; this.contentURL = `/v1/content/${id}/raw`;
this.fileInfoURL = `/v1/files/${id}`;
} }
static fromFileMetaData(fm: FileMetaData): FileDTO{ static fromFileMetaData(fm: FileMetaData): FileDTO{
@@ -53,12 +56,9 @@ function renameFileHandlerFactory(fileService: FileService): Handler {
const fileId: string = untypedCtx.params.fileId; const fileId: string = untypedCtx.params.fileId;
const reqBody = await ctx.request.body().value; const reqBody = await ctx.request.body().value;
const newPath = reqBody.path; const newPath = reqBody.path;
console.log("> " + reqBody)
if(!reqBody.path){ if(!reqBody.path){
return; return;
} }
console.log(">>> " + fileId);
console.log(">>> " + newPath);
const ret = await fileService.renameById(fileId, newPath); const ret = await fileService.renameById(fileId, newPath);
if(ret === null){ if(ret === null){
ctx.response.status = Status.NotFound; ctx.response.status = Status.NotFound;
@@ -98,6 +98,43 @@ function getDownloadFileHandlerFactory(fileService: FileService): Handler {
}; };
} }
function getDownloadTransformedFileHandlerFactory(fileService: FileService, waveFrontService: WaveFrontService): Handler {
function getScaleVector(params: any): Vector3D | null {
const x = parseFloat(params.get('scaleX') || 1);
const y = parseFloat(params.get('scaleY') || 1);
const z = parseFloat(params.get('scaleZ') || 1);
const ret: Vector3D = [x, y, z];
return ret.filter(isNaN).length === 0 ? ret : null
}
function getTranslateVector(params: any): Vector3D | null{
const x = parseFloat(params.get('translateX') || 0);
const y = parseFloat(params.get('translateY') || 0);
const z = parseFloat(params.get('translateZ') || 0);
const ret: Vector3D = [x, y, z];
return ret.filter(isNaN).length === 0 ? ret : null
}
return async (ctx: Context) => {
const untypedCtx: any = ctx;
const fileId = untypedCtx.params.fileId;
const reader = await fileService.getContentById(fileId);
if (reader == null) {
ctx.response.status = 404;
return;
}
const scale = getScaleVector(untypedCtx.request.url.searchParams);
const translate = getTranslateVector(untypedCtx.request.url.searchParams);
if(scale === null || translate === null){
ctx.response.status = Status.BadRequest;
return;
}
ctx.response.status = 200;
ctx.response.body = waveFrontService.scaleAndTranslate(reader, scale, translate);
};
}
function deleteFileHandlerFactory(fileService: FileService): Handler { function deleteFileHandlerFactory(fileService: FileService): Handler {
return async (ctx: Context) => { return async (ctx: Context) => {
const untypedCtx: any = ctx; const untypedCtx: any = ctx;
@@ -111,7 +148,7 @@ function deleteFileHandlerFactory(fileService: FileService): Handler {
}; };
} }
export function buildAPI(router: Router, fileService: FileService): Router { export function buildAPI(router: Router, fileService: FileService, waveFrontService: WaveFrontService): Router {
router.get('/files', listFileHandlerFactory(fileService)); router.get('/files', listFileHandlerFactory(fileService));
router.get('/files/:fileId', getFileHandlerFactory(fileService)); router.get('/files/:fileId', getFileHandlerFactory(fileService));
router.delete('/files/:fileId', deleteFileHandlerFactory(fileService)); router.delete('/files/:fileId', deleteFileHandlerFactory(fileService));
@@ -119,6 +156,8 @@ export function buildAPI(router: Router, fileService: FileService): Router {
router.post('/files', createFileHandlerFactory(fileService)); router.post('/files', createFileHandlerFactory(fileService));
router.get('/content/:fileId/raw', getDownloadFileHandlerFactory(fileService)); router.get('/content/:fileId/raw', getDownloadFileHandlerFactory(fileService));
router.get('/content/:fileId/transform', getDownloadTransformedFileHandlerFactory(fileService, waveFrontService));
return router; return router;
} }
+3 -1
View File
@@ -1,15 +1,17 @@
import { Application, Router } from './deps.ts'; import { Application, Router } from './deps.ts';
import { buildAPI } from './restapi.ts'; import { buildAPI } from './restapi.ts';
import { DenoKVFileService } from './services/mod.ts'; import { DenoKVFileService } from './services/mod.ts';
import { WaveFrontServiceImpl } from './services/wavefrontService.ts';
async function main() { async function main() {
const app = new Application(); const app = new Application();
const kv = await Deno.openKv(); const kv = await Deno.openKv();
const fileService = new DenoKVFileService(kv); const fileService = new DenoKVFileService(kv);
const waveFrontService = new WaveFrontServiceImpl();
const router = new Router({ prefix: '/v1' }); const router = new Router({ prefix: '/v1' });
app.use( app.use(
buildAPI(router, fileService).routes(), buildAPI(router, fileService, waveFrontService).routes(),
); );
await app.listen({ port: 9000 }); await app.listen({ port: 9000 });
+8
View File
@@ -39,3 +39,11 @@ export interface BlobService {
getBlob(index: string): Deno.Reader; getBlob(index: string): Deno.Reader;
getNumberOfChunks(index: string): Promise<number>; getNumberOfChunks(index: string): Promise<number>;
} }
export type Vector3D = [x: number, y: number, z: number];
export interface WaveFrontService {
scaleAndTranslate(objFile: Deno.Reader, scale: Vector3D, translate: Vector3D): Deno.Reader
}
+44
View File
@@ -0,0 +1,44 @@
import { TextLineStream, readableStreamFromReader, readerFromIterable } from "../deps.ts";
import { WaveFrontService, Vector3D } from "./interfaces.ts";
export type LineTransformer = (line: string) => string
const vectorLineRegexExp = /^v (?<x>-?\d+(\.\d+)?) (?<y>-?\d+(\.\d+)?) (?<z>-?\d+(\.\d+)?)\s*(#.*)?$/;
export class WaveFrontServiceImpl implements WaveFrontService {
private async *joinLineIterator(lines: AsyncIterable<string>, fn: LineTransformer): AsyncIterable<Uint8Array>{
const encoder = new TextEncoder();
for await(const line of lines){
yield encoder.encode(fn(line) + '\n')
}
}
getLineTransformer(scale: Vector3D, translate: Vector3D): LineTransformer {
return (line: string) => {
const match = line.match(vectorLineRegexExp);
if(match === null){
return line;
}
const x = parseFloat(match.groups!.x);
const y = parseFloat(match.groups!.y);
const z = parseFloat(match.groups!.z);
return `v ${x * scale[0] + translate[0]} ${y * scale[1] + translate[1]} ${z * scale[2] + translate[2]}`
}
}
transformLine(objFile: Deno.Reader, transformer: LineTransformer): Deno.Reader{
const lines = readableStreamFromReader(objFile)
.pipeThrough(new TextDecoderStream())
.pipeThrough(new TextLineStream());
return readerFromIterable(this.joinLineIterator(lines, transformer));
}
scaleAndTranslate(objFile: Deno.Reader, scale: Vector3D, translate: Vector3D): Deno.Reader {
const fn = this.getLineTransformer(scale, translate);
return this.transformLine(objFile, fn);
}
}
+59
View File
@@ -0,0 +1,59 @@
import { StringReader, readAll } from "../src/deps.ts";
import { Vector3D } from "../src/services/interfaces.ts";
import { WaveFrontServiceImpl } from "../src/services/wavefrontService.ts";
import { assertEquals } from 'https://deno.land/std/testing/asserts.ts';
Deno.test('WaveFrontServiceImpl happy path ', async (t) => {
const waveFrontService = new WaveFrontServiceImpl();
await t.step("test trivial transform", async () => {
const content = "line 1\nline2\nline3\n"
const reader = new StringReader(content);
const ret = waveFrontService.transformLine(reader, (s) => `hello ${s}`);
const expt = "hello line 1\nhello line2\nhello line3\n"
assertEquals(new TextDecoder().decode(await readAll(ret)), expt);
});
await t.step("test trivial transform (no final new line in original file) `", async () => {
const content = "line 1\nline2\nline3"
const reader = new StringReader(content);
const ret = waveFrontService.transformLine(reader, (s) => `hello ${s}`);
const expt = "hello line 1\nhello line2\nhello line3\n"
assertEquals(new TextDecoder().decode(await readAll(ret)), expt);
});
});
Deno.test('WaveFrontServiceImpl getLineTransformer', async (t) => {
const waveFrontService = new WaveFrontServiceImpl();
await t.step("trivial cases", () => {
const fn = waveFrontService.getLineTransformer([1, 2, 3], [10, 11, 12])
assertEquals(fn(''), '');
assertEquals(fn('hello'), 'hello');
assertEquals(fn('hello 1 2 3'), 'hello 1 2 3');
});
await t.step("transform cases", () => {
const scale: Vector3D = [5, 6, 7];
const translate: Vector3D = [10, 11, 12];
const fn = waveFrontService.getLineTransformer(scale, translate);
const expt = (x: number, y: number, z: number) => `v ${x * scale[0] + translate[0]} ${y * scale[1] + translate[1]} ${z * scale[2] + translate[2]}`;
const values = [-1, 2, 3, 10, 5.23, 34.56, -12.75 ]
for(const x of values){
for(const y of values){
for(const z of values){
assertEquals(fn(`v ${x} ${y} ${z}`), expt(x, y, z));
assertEquals(fn(`v ${x} ${y} ${z} `), expt(x, y, z));
assertEquals(fn(`v ${x} ${y} ${z} # foo bar`), expt(x, y, z));
}
}
}
});
})