implemented homework

This commit is contained in:
didier amyot committed 2023-10-19 11:48:30 -04:00
commit d7711ba95f
6 files changed
+597

No files matched your search

+131
View File
@@ -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
View File
Whitespace-only changes.
+52
View File
@@ -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()
+91
View File
@@ -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()
+299
View File
@@ -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;
}
+24
View File
@@ -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,
}