More or less finalized the REST api.
This commit is contained in:
1 parent
872f841d74
commit
95e58a67df
5 files changed
+159
-7
No files matched your search
+45
-6
@@ -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 { readerFromStreamReader } from './deps.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 { WaveFrontServiceImpl } from "./services/wavefrontService.ts";
|
||||
import { Vector3D, WaveFrontService } from './services/interfaces.ts';
|
||||
|
||||
type Handler = (ctx: Context) => void;
|
||||
|
||||
@@ -11,6 +12,7 @@ class FileDTO {
|
||||
readonly path: string;
|
||||
readonly id: string;
|
||||
readonly contentSize: number;
|
||||
readonly fileInfoURL: string;
|
||||
readonly contentURL: string;
|
||||
|
||||
constructor(path: string, id: string, size: number){
|
||||
@@ -18,6 +20,7 @@ class FileDTO {
|
||||
this.id = id;
|
||||
this.contentSize = size;
|
||||
this.contentURL = `/v1/content/${id}/raw`;
|
||||
this.fileInfoURL = `/v1/files/${id}`;
|
||||
}
|
||||
|
||||
static fromFileMetaData(fm: FileMetaData): FileDTO{
|
||||
@@ -53,12 +56,9 @@ function renameFileHandlerFactory(fileService: FileService): Handler {
|
||||
const fileId: string = untypedCtx.params.fileId;
|
||||
const reqBody = await ctx.request.body().value;
|
||||
const newPath = reqBody.path;
|
||||
console.log("> " + reqBody)
|
||||
if(!reqBody.path){
|
||||
return;
|
||||
}
|
||||
console.log(">>> " + fileId);
|
||||
console.log(">>> " + newPath);
|
||||
const ret = await fileService.renameById(fileId, newPath);
|
||||
if(ret === null){
|
||||
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 {
|
||||
return async (ctx: Context) => {
|
||||
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/:fileId', getFileHandlerFactory(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.get('/content/:fileId/raw', getDownloadFileHandlerFactory(fileService));
|
||||
router.get('/content/:fileId/transform', getDownloadTransformedFileHandlerFactory(fileService, waveFrontService));
|
||||
|
||||
|
||||
return router;
|
||||
}
|
||||
+3
-1
@@ -1,15 +1,17 @@
|
||||
import { Application, Router } from './deps.ts';
|
||||
import { buildAPI } from './restapi.ts';
|
||||
import { DenoKVFileService } from './services/mod.ts';
|
||||
import { WaveFrontServiceImpl } from './services/wavefrontService.ts';
|
||||
|
||||
async function main() {
|
||||
const app = new Application();
|
||||
const kv = await Deno.openKv();
|
||||
const fileService = new DenoKVFileService(kv);
|
||||
const waveFrontService = new WaveFrontServiceImpl();
|
||||
const router = new Router({ prefix: '/v1' });
|
||||
|
||||
app.use(
|
||||
buildAPI(router, fileService).routes(),
|
||||
buildAPI(router, fileService, waveFrontService).routes(),
|
||||
);
|
||||
|
||||
await app.listen({ port: 9000 });
|
||||
|
||||
@@ -39,3 +39,11 @@ export interface BlobService {
|
||||
getBlob(index: string): Deno.Reader;
|
||||
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
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
@@ -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));
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
})
|
||||
Reference in new issue
Block a user