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 { 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
@@ -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 });
|
||||||
|
|||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -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