implemented homework
This commit is contained in:
commit
d7711ba95f
6 files changed
+597
No files matched your search
+131
@@ -0,0 +1,131 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
lerna-debug.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# Diagnostic reports (https://nodejs.org/api/report.html)
|
||||
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
|
||||
|
||||
# Runtime data
|
||||
pids
|
||||
*.pid
|
||||
*.seed
|
||||
*.pid.lock
|
||||
|
||||
# Directory for instrumented libs generated by jscoverage/JSCover
|
||||
lib-cov
|
||||
|
||||
# Coverage directory used by tools like istanbul
|
||||
coverage
|
||||
*.lcov
|
||||
|
||||
# nyc test coverage
|
||||
.nyc_output
|
||||
|
||||
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
|
||||
.grunt
|
||||
|
||||
# Bower dependency directory (https://bower.io/)
|
||||
bower_components
|
||||
|
||||
# node-waf configuration
|
||||
.lock-wscript
|
||||
|
||||
# Compiled binary addons (https://nodejs.org/api/addons.html)
|
||||
build/Release
|
||||
|
||||
# Dependency directories
|
||||
node_modules/
|
||||
jspm_packages/
|
||||
|
||||
# Snowpack dependency directory (https://snowpack.dev/)
|
||||
web_modules/
|
||||
|
||||
# TypeScript cache
|
||||
*.tsbuildinfo
|
||||
|
||||
# Optional npm cache directory
|
||||
.npm
|
||||
|
||||
# Optional eslint cache
|
||||
.eslintcache
|
||||
|
||||
# Optional stylelint cache
|
||||
.stylelintcache
|
||||
|
||||
# Microbundle cache
|
||||
.rpt2_cache/
|
||||
.rts2_cache_cjs/
|
||||
.rts2_cache_es/
|
||||
.rts2_cache_umd/
|
||||
|
||||
# Optional REPL history
|
||||
.node_repl_history
|
||||
|
||||
# Output of 'npm pack'
|
||||
*.tgz
|
||||
|
||||
# Yarn Integrity file
|
||||
.yarn-integrity
|
||||
|
||||
# dotenv environment variable files
|
||||
.env
|
||||
.env.development.local
|
||||
.env.test.local
|
||||
.env.production.local
|
||||
.env.local
|
||||
|
||||
# parcel-bundler cache (https://parceljs.org/)
|
||||
.cache
|
||||
.parcel-cache
|
||||
|
||||
# Next.js build output
|
||||
.next
|
||||
out
|
||||
|
||||
# Nuxt.js build / generate output
|
||||
.nuxt
|
||||
dist
|
||||
|
||||
# Gatsby files
|
||||
.cache/
|
||||
# Comment in the public line in if your project uses Gatsby and not Next.js
|
||||
# https://nextjs.org/blog/next-9-1#public-directory-support
|
||||
# public
|
||||
|
||||
# vuepress build output
|
||||
.vuepress/dist
|
||||
|
||||
# vuepress v2.x temp and cache directory
|
||||
.temp
|
||||
.cache
|
||||
|
||||
# Docusaurus cache and generated files
|
||||
.docusaurus
|
||||
|
||||
# Serverless directories
|
||||
.serverless/
|
||||
|
||||
# FuseBox cache
|
||||
.fusebox/
|
||||
|
||||
# DynamoDB Local files
|
||||
.dynamodb/
|
||||
|
||||
# TernJS port file
|
||||
.tern-port
|
||||
|
||||
# Stores VSCode versions used for testing VSCode extensions
|
||||
.vscode-test
|
||||
|
||||
# yarn v2
|
||||
.yarn/cache
|
||||
.yarn/unplugged
|
||||
.yarn/build-state.yml
|
||||
.yarn/install-state.gz
|
||||
.pnp.*.idea
|
||||
.idea
|
||||
@@ -0,0 +1,52 @@
|
||||
import { LiftRequest} from "./shared.ts";
|
||||
|
||||
class ButtonClient{
|
||||
|
||||
private readonly host: string;
|
||||
|
||||
constructor(host: string) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
private async sendPost(url: string, payload: unknown): Promise<Response>{
|
||||
return await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
async listLiftRequests(): Promise<LiftRequest[]> {
|
||||
const resp = await fetch(`${this.host}/api/v1/lift-requests`);
|
||||
const ret = await resp.json();
|
||||
return ret as LiftRequest[] ;
|
||||
}
|
||||
|
||||
async createLiftRequest(direction: string, level: number): Promise<boolean> {
|
||||
const payload = {
|
||||
direction: direction,
|
||||
level: level,
|
||||
}
|
||||
const resp = await this.sendPost(`${this.host}/api/v1/lift-requests`, payload);
|
||||
return 199 < resp.status && resp.status < 300;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
async function experiment(): Promise<void>{
|
||||
const client = new ButtonClient("http://localhost:3000");
|
||||
console.log(await client.listLiftRequests());
|
||||
console.log(await client.createLiftRequest("UP", -1))
|
||||
console.log(await client.createLiftRequest("UP", -1))
|
||||
console.log(await client.listLiftRequests());
|
||||
console.log(await client.createLiftRequest("UP", 10))
|
||||
console.log(await client.createLiftRequest("UP", 10))
|
||||
console.log(await client.listLiftRequests());
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
await experiment()
|
||||
@@ -0,0 +1,91 @@
|
||||
import {Lift, LiftFilterCriteron, SmallLiftRepresentation} from "./shared.ts";
|
||||
import {h} from "https://esm.sh/stable/preact@10.8.1/denonext/preact.mjs";
|
||||
|
||||
class LiftClient{
|
||||
|
||||
private readonly host: string;
|
||||
|
||||
constructor(host: string) {
|
||||
this.host = host;
|
||||
}
|
||||
|
||||
async listElevators(): Promise<SmallLiftRepresentation[]> {
|
||||
const resp = await fetch(`${this.host}/api/v1/lifts`);
|
||||
const ret = await resp.json();
|
||||
return ret.lifts as SmallLiftRepresentation[] ;
|
||||
}
|
||||
|
||||
async filterLifts(criteron: LiftFilterCriteron): Promise<SmallLiftRepresentation[]> {
|
||||
const searchParams = new URLSearchParams(criteron);
|
||||
const resp = await fetch(`${this.host}/api/v1/lifts?${searchParams}`);
|
||||
const ret = await resp.json();
|
||||
return ret.lifts as SmallLiftRepresentation[];
|
||||
}
|
||||
|
||||
async getLiftById(id: number): Promise<Lift | null> {
|
||||
const resp = await fetch(`${this.host}/api/v1/lifts/${id}`);
|
||||
if(resp.status === 404){
|
||||
return null;
|
||||
}
|
||||
return (await resp.json()) as Lift;
|
||||
}
|
||||
|
||||
private async sendPost(url: string, payload: unknown): Promise<Response>{
|
||||
return await fetch(url, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
}
|
||||
|
||||
async moveLift(liftId: number, newLevel: number): Promise<boolean> {
|
||||
const payload = {
|
||||
type: 'lift-move',
|
||||
level: newLevel,
|
||||
}
|
||||
const resp = await this.sendPost(`${this.host}/api/v1/lifts/${liftId}`, payload);
|
||||
return 199 < resp.status && resp.status < 300;
|
||||
}
|
||||
|
||||
async openLiftDoor(liftId: number): Promise<boolean> {
|
||||
const lift = await this.getLiftById(liftId);
|
||||
if(lift == null){
|
||||
return false;
|
||||
}
|
||||
const payload = {
|
||||
type: 'door-open',
|
||||
level: lift.level,
|
||||
}
|
||||
const resp = await this.sendPost(`${this.host}/api/v1/lifts/${liftId}`, payload);
|
||||
return 199 < resp.status && resp.status < 300;
|
||||
}
|
||||
|
||||
async setDestination(liftId: number, newDestination: number): Promise<boolean> {
|
||||
const payload = {
|
||||
type: 'add-destination',
|
||||
destination: newDestination,
|
||||
}
|
||||
const resp = await this.sendPost(`${this.host}/api/v1/lifts/${liftId}`, payload);
|
||||
return 199 < resp.status && resp.status < 300;
|
||||
}
|
||||
}
|
||||
|
||||
async function experiment(): Promise<void>{
|
||||
const client = new LiftClient("http://localhost:3000");
|
||||
console.log(await client.getLiftById(2));
|
||||
console.log(await client.setDestination(2, -1))
|
||||
console.log(await client.setDestination(2, 11))
|
||||
console.log(await client.setDestination(2, 16))
|
||||
console.log(await client.setDestination(2, 14))
|
||||
console.log(await client.setDestination(2, -1))
|
||||
|
||||
|
||||
console.log(await client.getLiftById(2));
|
||||
console.log(await client.openLiftDoor(2))
|
||||
console.log(await client.getLiftById(2));
|
||||
}
|
||||
|
||||
|
||||
await experiment()
|
||||
@@ -0,0 +1,299 @@
|
||||
// @deno-types="npm:@types/express@4.17.20"
|
||||
import express, {Response, Request} from "npm:express@4.18.2";
|
||||
import bodyParser from "npm:body-parser@1.20.2";
|
||||
import {Lift, LiftFilterCriteron, LiftRequest, SmallLiftRepresentation} from "./shared.ts";
|
||||
import c from "https://esm.sh/v132/@babel/runtime@7.22.15/denonext/helpers/getPrototypeOf.js";
|
||||
|
||||
const app = express()
|
||||
app.use(bodyParser.json());
|
||||
|
||||
const port = 3000;
|
||||
|
||||
type LiftSearchRequestParams = {
|
||||
floor?: string,
|
||||
min_floor?: string,
|
||||
max_floor?: string,
|
||||
direction?: string,
|
||||
}
|
||||
|
||||
type LiftAction = {
|
||||
type: string,
|
||||
level?: number,
|
||||
destination?: number,
|
||||
}
|
||||
|
||||
class ButtonService{
|
||||
private readonly liftRequests : LiftRequest[] = [];
|
||||
|
||||
getLiftRequests(): LiftRequest[]{
|
||||
return this.liftRequests;
|
||||
}
|
||||
|
||||
addLiftRequest(liftRequest: LiftRequest): boolean{
|
||||
for(const lr of this.liftRequests){
|
||||
if(lr.level === liftRequest.level && lr.direction === liftRequest.direction){
|
||||
return false;
|
||||
}
|
||||
}
|
||||
this.liftRequests.push(liftRequest);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
class LiftService {
|
||||
|
||||
private readonly lifts: Lift[];
|
||||
|
||||
constructor(lifts: Lift[]) {
|
||||
this.lifts = lifts;
|
||||
}
|
||||
|
||||
getAllLifts(): Lift[] {
|
||||
return this.lifts;
|
||||
}
|
||||
|
||||
private computeDirection(lift: Lift): string{
|
||||
if(lift.destinations.length === 0){
|
||||
return "IDLE";
|
||||
}
|
||||
const nextDestination = lift.destinations[0];
|
||||
if(nextDestination === lift.level){
|
||||
return lift.direction;
|
||||
}
|
||||
if(nextDestination > lift.level){
|
||||
return "UP";
|
||||
}
|
||||
return "DOWN";
|
||||
}
|
||||
|
||||
private isBetween(x: number, min_: number, max_: number): boolean{
|
||||
return min_ < x && x < max_;
|
||||
}
|
||||
|
||||
private addDestination(destinations: number[], currentLevel: number, newDestination: number){
|
||||
if(destinations.length === 0){
|
||||
return [newDestination];
|
||||
}
|
||||
if(destinations.includes(newDestination)){
|
||||
return destinations;
|
||||
}
|
||||
|
||||
const firstDestination = destinations[0];
|
||||
if(this.isBetween(newDestination, currentLevel, firstDestination)){
|
||||
return [newDestination].concat(destinations);
|
||||
|
||||
}
|
||||
for(let i = 0; i < destinations.length - 1; i++){
|
||||
const first = destinations[i];
|
||||
const second = destinations[i + 1];
|
||||
if(this.isBetween(newDestination, first, second)){
|
||||
const firstPart = destinations.slice(0, i + 1);
|
||||
const secondPart = destinations.slice(i + 1);
|
||||
return firstPart.concat([newDestination]).concat(secondPart);
|
||||
}
|
||||
}
|
||||
return destinations.concat([newDestination]);
|
||||
|
||||
}
|
||||
|
||||
processAddDestination(liftId: number, level: number): boolean {
|
||||
const lift = this.getElevatorById(liftId);
|
||||
if(!lift){
|
||||
return false;
|
||||
}
|
||||
lift.destinations = this.addDestination(lift.destinations, lift.level, level);
|
||||
lift.direction = this.computeDirection(lift);
|
||||
return true;
|
||||
}
|
||||
|
||||
processDoorOpen(liftId: number, level: number): boolean {
|
||||
const lift = this.getElevatorById(liftId);
|
||||
if(!lift){
|
||||
return false;
|
||||
}
|
||||
lift.destinations = this.removeFromArray(lift.destinations, level);
|
||||
lift.direction = this.computeDirection(lift);
|
||||
return true;
|
||||
}
|
||||
removeFromArray(lst: number[], toRemove: number): number[] {
|
||||
const ret: number[] = [];
|
||||
for(const e of lst){
|
||||
if(e !== toRemove){
|
||||
ret.push(e);
|
||||
}
|
||||
}
|
||||
return ret
|
||||
}
|
||||
|
||||
processLiftMovement(liftId: number, level: number): boolean {
|
||||
const lift = this.getElevatorById(liftId);
|
||||
if(!lift){
|
||||
return false;
|
||||
}
|
||||
lift.level = level;
|
||||
return true;
|
||||
}
|
||||
|
||||
private matches(lift: Lift, liftFilterCriteria: LiftFilterCriteron) {
|
||||
if (liftFilterCriteria.floor !== undefined && lift.level != liftFilterCriteria.floor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (liftFilterCriteria.min_floor !== undefined && lift.level < liftFilterCriteria.min_floor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (liftFilterCriteria.max_floor !== undefined && lift.level > liftFilterCriteria.max_floor) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (liftFilterCriteria.direction !== undefined && lift.direction !== liftFilterCriteria.direction) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
getAllListsMatching(liftFilterCriteria: LiftFilterCriteron): Lift[] {
|
||||
const ret: Lift[] = [];
|
||||
for (const lift of this.lifts) {
|
||||
if (this.matches(lift, liftFilterCriteria)) {
|
||||
ret.push(lift);
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
getElevatorById(id: number): Lift | null {
|
||||
for (const lift of this.lifts) {
|
||||
if (lift.id === id) {
|
||||
return lift;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function createLiftService(): LiftService {
|
||||
const defaultLifts: Lift[] = [
|
||||
{id: 1, level: 12, direction: 'IDLE', destinations: []},
|
||||
{id: 2, level: -1, direction: 'IDLE', destinations: []},
|
||||
{id: 3, level: 5, direction: 'IDLE', destinations: []},
|
||||
{id: 4, level: 17, direction: 'IDLE', destinations: []},
|
||||
];
|
||||
return new LiftService(defaultLifts);
|
||||
}
|
||||
|
||||
function toSmallRepresentation(lifts: Lift[]): SmallLiftRepresentation[] {
|
||||
const ret: SmallLiftRepresentation[] = [];
|
||||
for (const lift of lifts) {
|
||||
ret.push({id: lift.id, level: lift.level});
|
||||
}
|
||||
return ret;
|
||||
|
||||
}
|
||||
|
||||
const liftService = createLiftService();
|
||||
|
||||
const buttonService = new ButtonService();
|
||||
|
||||
function send404(res: Response): void {
|
||||
res.status(404);
|
||||
res.send({});
|
||||
}
|
||||
|
||||
function send400(res: Response): void {
|
||||
res.status(400);
|
||||
res.send({});
|
||||
}
|
||||
|
||||
app.get('/api/v1/lifts', (req: Request, res: Response): void => {
|
||||
const query = req.query as LiftSearchRequestParams;
|
||||
const criterion = liftSearchRequestParamsToLiftFilterCriteria(query);
|
||||
const ret = liftService.getAllListsMatching(criterion);
|
||||
res.send({lifts: toSmallRepresentation(ret)})
|
||||
});
|
||||
|
||||
app.get('/api/v1/lifts/:id', (req: Request<{ id: string }>, res: Response): void => {
|
||||
const id = req.params.id;
|
||||
const ret = liftService.getElevatorById(parseInt(id));
|
||||
if (ret == null) {
|
||||
return send404(res);
|
||||
}
|
||||
res.send(ret)
|
||||
});
|
||||
|
||||
app.post('/api/v1/lifts/:id', (req: Request<{ id: string }>, res: Response): void => {
|
||||
const id = parseInt(req.params.id);
|
||||
const ret = liftService.getElevatorById(id);
|
||||
if (ret == null) {
|
||||
return send404(res);
|
||||
}
|
||||
const action = req.body as LiftAction | null;
|
||||
if(!action || !isValidAction(action)){
|
||||
return send400(res)
|
||||
}
|
||||
if(action.type === 'lift-move'){
|
||||
liftService.processLiftMovement(id, action.level!);
|
||||
}
|
||||
if(action.type === 'door-open'){
|
||||
liftService.processDoorOpen(id, action.level!);
|
||||
}
|
||||
if(action.type === 'add-destination'){
|
||||
liftService.processAddDestination(id, action.destination!);
|
||||
}
|
||||
|
||||
res.send(ret)
|
||||
});
|
||||
|
||||
app.get('/api/v1/lift-requests', (req: Request, res: Response): void => {
|
||||
res.send(buttonService.getLiftRequests());
|
||||
});
|
||||
|
||||
app.post('/api/v1/lift-requests', (req: Request, res: Response): void => {
|
||||
const changed = buttonService.addLiftRequest(req.body as LiftRequest);
|
||||
res.send({changed: changed});
|
||||
});
|
||||
|
||||
app.listen(port, () => {
|
||||
console.log(`Example app listening on port ${port}`)
|
||||
})
|
||||
|
||||
function liftSearchRequestParamsToLiftFilterCriteria(query: LiftSearchRequestParams): LiftFilterCriteron {
|
||||
const ret: LiftFilterCriteron = {};
|
||||
if (query.direction !== undefined) {
|
||||
ret.direction = query.direction;
|
||||
}
|
||||
if (query.floor !== undefined) {
|
||||
ret.floor = parseInt(query.floor);
|
||||
}
|
||||
if (query.max_floor !== undefined) {
|
||||
ret.max_floor = parseInt(query.max_floor);
|
||||
}
|
||||
if (query.min_floor !== undefined) {
|
||||
ret.min_floor = parseInt(query.min_floor);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
function isLevel(x: any): boolean{
|
||||
const allLevels = [
|
||||
-2, -1, 1, 2, 3, 4, 5,
|
||||
6, 7, 8, 9, 10, 11, 12,
|
||||
13, 14, 15, 16, 17, 18];
|
||||
return allLevels.includes(x);
|
||||
}
|
||||
|
||||
function isValidAction(action?: LiftAction): boolean {
|
||||
if(!action){
|
||||
return false;
|
||||
}
|
||||
if(action.type === "lift-move" || action.type === "door-open" ){
|
||||
return isLevel(action.level);
|
||||
}
|
||||
if(action.type === "add-destination" ){
|
||||
return isLevel(action.destination);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
export type Lift = {
|
||||
id: number,
|
||||
level: number,
|
||||
direction: string,
|
||||
destinations: number[]
|
||||
}
|
||||
|
||||
export type SmallLiftRepresentation = {
|
||||
id: number,
|
||||
level: number,
|
||||
}
|
||||
|
||||
export type LiftFilterCriteron = {
|
||||
floor?: number,
|
||||
min_floor?: number,
|
||||
max_floor?: number,
|
||||
direction?: string,
|
||||
}
|
||||
|
||||
|
||||
export type LiftRequest = {
|
||||
direction: string,
|
||||
level: number,
|
||||
}
|
||||
Reference in new issue
Block a user