From 5aab318e79b6404584acf13f177cd840d4132209 Mon Sep 17 00:00:00 2001 From: Lightemerald Date: Thu, 23 Nov 2023 16:01:56 +0100 Subject: [PATCH] FIrst commit --- .eslintrc.json | 55 +++++++ .gitignore | 175 ++++++++++++++++++++++ Classes/Computer.js | 109 ++++++++++++++ Classes/Database.js | 52 +++++++ README.md | 15 ++ Routes/computer.js | 83 +++++++++++ Routes/user.js | 67 +++++++++ bun.lockb | Bin 0 -> 72775 bytes database.sql | 33 +++++ index.js | 40 +++++ jsconfig.json | 22 +++ package.json | 18 +++ public/c/index.html | 354 ++++++++++++++++++++++++++++++++++++++++++++ public/index.html | 170 +++++++++++++++++++++ 14 files changed, 1193 insertions(+) create mode 100644 .eslintrc.json create mode 100644 .gitignore create mode 100644 Classes/Computer.js create mode 100644 Classes/Database.js create mode 100644 README.md create mode 100644 Routes/computer.js create mode 100644 Routes/user.js create mode 100755 bun.lockb create mode 100644 database.sql create mode 100644 index.js create mode 100644 jsconfig.json create mode 100644 package.json create mode 100644 public/c/index.html create mode 100644 public/index.html diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 0000000..a3a88f7 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,55 @@ +{ + "extends": [ + "eslint:recommended", + "plugin:security/recommended" + ], + "env": { + "node": true, + "es6": true + }, + "parserOptions": { + "ecmaVersion": 2022 + }, + "rules": { + "arrow-spacing": ["warn", { "before": true, "after": true }], + "brace-style": ["error", "stroustrup", { "allowSingleLine": true }], + "comma-dangle": ["error", "always-multiline"], + "comma-spacing": "error", + "comma-style": "error", + "curly": ["error", "multi-line", "consistent"], + "dot-location": ["error", "property"], + "handle-callback-err": "off", + "indent": ["error", "tab"], + "keyword-spacing": "error", + "max-nested-callbacks": ["error", { "max": 4 }], + "max-statements-per-line": ["error", { "max": 2 }], + "no-console": "off", + "no-empty-function": "error", + "no-floating-decimal": "error", + "no-inline-comments": "error", + "no-lonely-if": "error", + "no-multi-spaces": "error", + "no-multiple-empty-lines": ["error", { "max": 2, "maxEOF": 1, "maxBOF": 0 }], + "no-shadow": ["error", { "allow": ["err", "resolve", "reject"] }], + "no-trailing-spaces": ["error"], + "no-var": "error", + "object-curly-spacing": ["error", "always"], + "prefer-const": "error", + "quotes": ["error", "single"], + "semi": ["error", "always"], + "space-before-blocks": "error", + "space-before-function-paren": ["error", { + "anonymous": "never", + "named": "never", + "asyncArrow": "always" + }], + "space-in-parens": "error", + "space-infix-ops": "error", + "space-unary-ops": "error", + "spaced-comment": "error", + "yoda": "error", + "security/detect-non-literal-fs-filename": "off", + "security/detect-non-literal-require": "off", + "security/detect-object-injection": "off" + } +} \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..468f82a --- /dev/null +++ b/.gitignore @@ -0,0 +1,175 @@ +# Based on https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore + +# Logs + +logs +_.log +npm-debug.log_ +yarn-debug.log* +yarn-error.log* +lerna-debug.log* +.pnpm-debug.log* + +# Caches + +.cache + +# 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/) + +.parcel-cache + +# Next.js build output + +.next +out + +# Nuxt.js build / generate output + +.nuxt +dist + +# Gatsby files + +# 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 + +# 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.* + +# IntelliJ based IDEs +.idea + +# Finder (MacOS) folder config +.DS_Store diff --git a/Classes/Computer.js b/Classes/Computer.js new file mode 100644 index 0000000..6da1cd3 --- /dev/null +++ b/Classes/Computer.js @@ -0,0 +1,109 @@ +import { database } from './Database'; + +class Computer { + constructor(id, brand, model, state, status) { + this.id = id; + this.brand = brand; + this.model = model; + this.status = status; + this.state = state; + } + + async get (id) { + try { + const rows = await database.execute('SELECT * FROM computers WHERE id = ?', [id]); + if (rows.length === 0) { + throw new Error('Computer not found'); + } else { + this.id = rows[0].id; + this.brand = rows[0].brand; + this.model = rows[0].model; + this.status = rows[0].status; + this.state = rows[0].state; + } + } catch (error) { + console.error('Error getting computer:', error); + throw error; + } + } + + async create() { + try { + const result = await database.execute( + 'INSERT INTO computers (brand, model, state, status) VALUES (?, ?, ?, ?)', + [this.brand, this.model, this.state, this.status] + ); + this.id = result.insertId; + } catch (error) { + console.error('Error creating computer:', error); + throw error; + } + } + + async update() { + try { + await database.execute( + 'UPDATE computers SET brand = ?, model = ?, state = ?, status = ? WHERE id = ?', + [this.brand, this.model, this.state, this.status, this.id] + ); + } catch (error) { + console.error('Error updating computer:', error); + throw error; + } + } + + async delete() { + try { + await database.execute('DELETE FROM computers WHERE id = ?', [this.id]); + } catch (error) { + console.error('Error deleting computer:', error); + throw error; + } + } +} + +// Computer list class +class Computers extends Array { + + async getAll() { + try { + const rows = await database.execute('SELECT * FROM computers'); + this.length = 0; // Clear the existing array + rows.forEach(row => { + const computer = new Computer(row.id, row.brand, row.model, row.state, row.status); + this.push(computer); + }); + } catch (error) { + console.error('Error getting computers:', error); + throw error; + } + } + + async create(brand, model, state, status) { + try { + const computer = new Computer(null, brand, model, state, status); + await computer.create(); + this.push(computer); + } catch (error) { + console.error('Error creating computer:', error); + throw error; + } + } + + async delete(id) { + try { + const computerIndex = this.findIndex(computer => computer.id === id); + if (computerIndex === -1) { + throw new Error('Computer not found'); + } + const computer = this[computerIndex]; + await computer.delete(); + this.splice(computerIndex, 1); + } catch (error) { + console.error('Error deleting computer:', error); + throw error; + } + } +} + +export { Computer, Computers }; diff --git a/Classes/Database.js b/Classes/Database.js new file mode 100644 index 0000000..8b32a9f --- /dev/null +++ b/Classes/Database.js @@ -0,0 +1,52 @@ + +const mysql = require('mysql2/promise'); + +class Database { + constructor(config) { + this.pool = mysql.createPool(config); + } + + async query(sql, params) { + const connection = await this.pool.getConnection(); + try { + const [rows] = await connection.query(sql, params); + return rows; + } finally { + connection.release(); + } + } + + async execute(sql, params) { + const connection = await this.pool.getConnection(); + try { + const [result] = await connection.execute(sql, params); + return result; + } finally { + connection.release(); + } + } + + async transaction(callback) { + const connection = await this.pool.getConnection(); + try { + await connection.beginTransaction(); + const result = await callback(connection); + await connection.commit(); + return result; + } catch (error) { + await connection.rollback(); + throw error; + } finally { + connection.release(); + } + } +} + +const database = new Database({ + host: process.env.DB_HOST, + user: process.env.DB_USER, + password: process.env.DB_PASS, + database: process.env.DB_NAME +}); + +export { Database, database }; \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..c96932c --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# inventory + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.js +``` + +This project was created using `bun init` in bun v1.0.13. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/Routes/computer.js b/Routes/computer.js new file mode 100644 index 0000000..03a409c --- /dev/null +++ b/Routes/computer.js @@ -0,0 +1,83 @@ +import express from 'express'; +import { Computer, Computers } from '../Classes/Computer'; + +const router = express.Router(); + +// GET all computers +router.get('/', async (req, res) => { + const computers = new Computers(); + await computers.getAll(); + try { + + res.status(200).json(computers); + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + +// GET a computer by ID +router.get('/:id', async (req, res) => { + try { + const computer = new Computer(); + await computer.get(req.params.id); + if (computer.brand === null) { + res.status(404).json({ error: 'Computer not found' }); + } else { + res.status(200).json(computer); + } + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + +// POST a new computer +router.post('/', async (req, res) => { + try { + const { brand, model, state, status } = req.body; + if (!brand || !model || !state || !status) { + res.status(400).json({ error: 'Bad Request' }); + } + const computer = new Computer(null, brand, model, state, status); + await computer.create(); + res.status(201).json({ message: 'Computer created successfully' }); + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + +// PUT (update) a computer by ID +router.put('/:id', async (req, res) => { + try { + const computer = new Computer(); + await computer.get(req.params.id); + if (computer.brand === null) { + res.status(404).json({ error: 'Computer not found' }); + } + const { brand, model, state, status } = req.body; + computer.brand = brand; + computer.model = model; + computer.state = state; + computer.status = status; + await computer.update(); + res.status(200).json({ message: 'Computer updated successfully' }); + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + +// DELETE a computer by ID +router.delete('/:id', async (req, res) => { + try { + const computer = new Computer(); + await computer.get(req.params.id); + if (computer.brand === null) { + res.status(404).json({ error: 'Computer not found' }); + } + await computer.delete(); + res.status(200).json({ message: 'Computer deleted successfully' }); + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + +export default router; \ No newline at end of file diff --git a/Routes/user.js b/Routes/user.js new file mode 100644 index 0000000..e2d9391 --- /dev/null +++ b/Routes/user.js @@ -0,0 +1,67 @@ +import express from 'express'; +import { database } from '../Classes/Database'; +import jwt from 'jsonwebtoken'; + +const router = express.Router(); + +// POST to login using username and password +router.post('/login', async (req, res) => { + try { + const { username, password } = req.body; + if (!username || !password) { + res.status(400).json({ error: 'Bad Request' }); + } + const rows = await database.execute('SELECT * FROM users WHERE username = ?', [username]); + if (rows.length === 0) { + res.status(401).json({ error: 'Unauthorized' }); + } else { + const isPasswordValid = await Bun.password.verify(password, rows[0].password); + if (!isPasswordValid) { + res.status(401).json({ error: 'Unauthorized' }); + } else { + const token = jwt.sign({ username, password }, process.env.SECRET); + res.status(200).json({ token }); + } + } + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + +// POST to check if token is valid +router.post('/verify', async (req, res) => { + try { + const token = req.body.token; + if (!token) { + res.status(401).json({ error: 'Unauthorized' }); + } else { + jwt.verify(token, process.env.SECRET, async (err, decoded) => { + if (err) res.status(401).json({ error: 'Unauthorized' }); + const rows = await database.execute('SELECT * FROM users WHERE username = ?', [decoded.username]); + if (rows.length === 0) res.status(401).json({ error: 'Unauthorized' }); + const isPasswordValid = await Bun.password.verify(decoded.password, rows[0].password); + if (!isPasswordValid) res.status(401).json({ error: 'Unauthorized' }); + res.status(200).json({ message: 'Authorized' }); + }); + } + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + +// POST to register a new user +router.post('/register', async (req, res) => { + try { + const { username, password } = req.body; + if (!username || !password) { + res.status(400).json({ error: 'Bad Request' }); + } + const hashedPassword = await Bun.password.hash(password); + await database.execute('INSERT INTO users (username, password) VALUES (?, ?)', [username, hashedPassword]); + res.status(201).json({ message: 'User created successfully' }); + } catch (error) { + res.status(500).json({ error: 'Internal Server Error' }); + } +}); + +export default router; \ No newline at end of file diff --git a/bun.lockb b/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..ab6d2e96b0cf605e0933359f7e366f2580e2af4a GIT binary patch literal 72775 zcmY#Z)GsYA(of3F(@)JSQ%EY!<4P*c)6L0G&Q8nBN!3luFUn0U(JeFJVq#!mC|UgG z#iQ=uySPN6HYX$|ui99rA$6PK)jcn@XN-m+Ykjv%Gctg{ArQg9z`%hhBkiogdARYt5T&Q{rR)~5T28cbyskvpTMGOou_f#h4N|1sSZisy)l?AD~nZ*q9JP?28X6B~qrX(?l@Id5? zOA<>;i&KlkxEUDu7#JG7xMBXu1N+|&%7=vyNI%GYE~xoi`5@{u^D(n|gnt&w7m|V4pOada2MT`<8HoH-sJi#k3=ERs^qpD&itl|=kaV?GiUFK|gybOn zGC7F83^_ql92FAODrxa zWME(@N-ZtUOsix_(uerh*Z`6)>J1?7Q#OF8-zv$#AjiPaFiVnwL7IV~VW%MjgA4;h z!vsl)I~t(oE;oXhGusGaPN*@2_ArLH`>`=3+|nf>>DCi!t}wJ*=Y*EyrAm)n-bOgz zGKud$`s4KzQICBw%mpVC%r<;`zI}z)NuwK_i{AanDlLM+GCMZ(AUgh@=)s3u_arMT-|!T-{WvP zx36YW-aWL_k6RLrK`7o-tb|{Zn00& z%NIVM|J_^!W?z_E*DI~o#JhRo`Zhmzd^IbxpHyY{Gk`_c>zHeyUcQ{bgWD-rPWE(! zJ$z@f)=h6}ar__I2|qd<^%*Bmk2{_C*&(86*;}@(cvQu2zV1t3 z-o$f9Dj%B`3GCng>1WIY@rvfO+_|66@{0Y4`q1;B*0s`U*6xF@yl>XqE)>}ss^MYZ z&DrVo;KJ;gr=$vVcW|@MnRrII`Pm{_nZNZDS3N&zANQ&3m-GBJAGUW)n91?fViDhr zou$3I9sb$}p4^c!edbk}19yVA99fckWO1GSy&HkgS3eET+cZ<8`pY%T`qc#y+-2$Y zSMUGResWQ8!aB3~w|=+Ee&rSH)5?n0kJ@{gMWrwD^~?$l-PyDEXm7Wx+-NoV@{%Wp z!Eafm6FyIDi53gE>+E>vZs4z#I;XC_S$jd)wZbH(u3@`s6D%FBIAMCkch|Gr7JrUB zo93BOR}QedJpZ|4N)c=Jvlk8%e(Vfss9rCy;z#JshuT8&-c_$F->=h;$fkan@`qHa!OyLy2D}qId6tN zUvDa(Rc-Nj;~*-qad%aJ-VAxBM{$Ag-W5ddf5$rK-dl~6y)}H|Ojj$y1F!hTemeOu z@2BMt->e|%Fe#6V@*g*!?7P{`|8wygCMN!OSHlk7J0{Y7>rv>Lqf5(M(w^!0mzr|T zeR!*d{jsq=KSz@M#Lcf>1sywiBXwEUkw}B@Ts_$2H>{${(ijul!M!^R>@AJ}tcWom8Zs>(SG%w${b_6rR}NG;>Bp zBG-{QH_xfpl>HW6s&nY|qYGD9pHDT*o3W1P-wUNL-%CVi=4#3%t(ubLe{sS6E9R#) zdbtr$m$T>2#?5Qa=I|6L8T z;XMDi(ZEM6=7#IxIo$DbROf9t7NgzOrfHYdY0k$Qd$U9BpIvkZXN&z6X|D?>+&4+edmj{5X5X(f z`<8X6gjd-kugeC0y|Mf%t|9hQbz;u)_+E+H`d+i0U2~rA;!Vq~xr%c(n z=hn&lR)*doQjPc5*!mSz%C;V|vFiw2^fNxEMvOI5)=#$iWNO{Z@}kIx%l+40EPs1l z()8Z8)%?jpOBcNPk#sj+n8ReXV#K0ft^ZYC^C(DiH>nyu?6_UB(eD&T@t)At>zMdI zR=-I<+adH?X})TMj&^Hm5~M!?s?o{Apk50|rv?K9g9!se12Y2y0~-Sa1BeER6T&e4 zOBfg!tQZ&?Kzxv15GJG-Bo9&#qVGZVgV-R<3L=QWF#QsY3=BRD3=N?00AW(?Phn(W zux4OrU}s=p5F*3>^BE!jhxO=4b^lF91_lELh6a#6ke^6(KPM9dgAD^i11QMIiN8c9 z1_lRc{DHy`Ho&Wz+eZBKV)$b8y|-0H(`dP zUl5;IyFuoH)Pv{@W(EdRsQp|Z0U|L>{~l&Y_`~dmi4!sgrtSwb#Qz{ZDEtWNgUN%` zgV<&)3=9@f_k-L3!XR-%7^Xju1(NA1M5Y zO~Ww#TgY<#&4#*Gq^ugp{`ZakN7;K^CKgbMam;wScOn({=!hRD1dXS}I`seaM{11{N zC;i{%fz-b+^FV$8nMDZ0)C==6Ft|X|4@``ZUXVOYZ8$FjgCkTwKS+W|4AVb@mw~|$ z+I}Q9?m$++^q=H~)PEp~B*e^B}ZnMn)=*#}Zr zj$}WGPb`M%KgfshKS(dB{%7PzO+TP80GUGw!|b=@hqS*y>S1Llh)oE?^k?%U!VlDk zA=Uk}`62NK(?@I`g4utQAJTq->4%9E>waMYNc#n(pIE&hvtepI1t8%M(hqVw2!rGa zVVM3#0R{$L28ISu+9S5^0GSQbze@l${g4{}a)JyDuF(9?%fP?@@(0L#LKtR$vLFM4 z6I4G)41@{k1k;dNH0hprhlObBK|;qFpwKS@`Nx<|81y#kb0OHh)oE? z)QXBi>Ti&Gklm!ZKT;G?eu4DE%mtZ;55v^;i$ca52O~R|D+hg{~&Q<&4B6W z6o=$rm|jxL?{IMj2774!6(OVio+b{dzd-H*xg8dcAot+IAoU>mbK;QpH;4^#y99_q z1cvDsmw>cCK=mfbZkRYBBVg)0B@p!wNIwV@(hHJ@>F<_67~WoJ^Qnb!muxklR6uNe#bT zsD4n|AxHm8sD6-MQ29%0_$kRi@;}T!Fn@r|!-rw&vt%Ik7f3BA{gUecSu&9DgXss+ z#9)~H*P!}A_JYJfG%5OpWD(^zNI$9mkCBC>e~_IZyFp<9ViUq3yFh9t$wJ0|NYM+@ z2h)E;7E=F$;vHlLOdO;JABL$@lw)9Uhn9aJ{lvDRVfq{75b+1H4`c?2CWK-7_sBu& zZaytlv%prt9>OpGW zDM0!UAT~%ZC>%j-LKvoBM-kHg0hOP`x*cRTOn-tRB>#|NH?sa&ijepN_5H*_5=3H{ z{kIh%^PeDXp!Nq$9K^?mVd}(`AmIl}`yll&aS$6HhN%ltg2X>4{P3wqmxJkF0JWdk zc!QY((|-@DALf3TJRusUUq%@c{^YtpL>c0LP}%{d0Ydh|55x3ps6y-q=_4f#!t_U|Li`WX53&y?4r1fOFm*kukn{)B z4~s{9dO>n9{YO+G=?A7CBu5N}>1R@d_#fnVP#yr$r0BO*gV+y?H)7oZv%gvmk$*s9 zpf)He_HR^!w0}VMgX{*;r0D++wI5_RNFS;Cb=4v62iXk@Kad&3V3>Wm>X7^g(o3w} zAoF4RSE@tOA1K^FX$RyEkUSv_)Bj8z(*Fh74T^gZO-L_D9;V+!15*FN+z%56slkU~ z>LzM1Fc>p1G=TbFptd7S9G_m098BF24M_V9R(`<5L2B?}m^xlfi2F(LJIGv^es@jC z_!&q)NSs&<(?4AklK+VfcbNGw{pU3y{dbW4ATvNTObtF7rk_y@b^aeFj!!R04yI05 z3zGjq;SUlAxdB8I!Z7_)v>@(>@nJNv`j4UMC!`N153}co7Nq=u@nQ0WXqb9$ZOHfs zvHpgc3)A1Ljfj6x-UVTp8hkWN|50s7_<`abq!%WRPcKLgrcOWy(*6gdXhIlf|2=)k{2z!9as#LyC8QT54^j`J?F}IHA4o4u z3?xnn!_@W}K*~Rueoz`Bq!%O)(|^SP68L)hc!_0>1UuX;|zhUl%(J*zmXqf&t#*p?K$nC`H1(^xc zuV%u);0ay7Mb7%+d=mzSAgF#)!T@If9TUj-4b1JNre9@KNc{!McOWxC800@vU9c-QonFfhVLd^rwAiflo z528Wwqrw1LBcTeV)u8g!pg~~=+D`#85=4X2ls42J9grXcxUCEtGG_3G@{wtfeqX41 zKd3xD8kAmw7{L1o8RDVpK{UwU2~a+W28ADJk0nTm5*lP~8g!3DCde=b1_lrfatCOi zFGwD=Cln-wjRu7WXpbdGeJRwRpxv4veW3l~Ao(iLUQY%Fh8mC|MAD!%*bG&Vj|SP( z0@PMzQ@i84L4x&M6WG|EtqCw{EgYrQ%sQ-Kf$_LRP{l}o@ zABT$Lqe14KfcpOwR6RZ#r2aHi9Wo6PKLZuVM}yRzg{lM5ApSY1{THC(AQ~io5$cai zQ2H`d9z=uWuRznqb*TIeXj>MvzXzlgM1$1b2XPn}7(g^Ad>%siAQ~k92+GGtgVa5S zssquWbn^_#2hkw;=TJU~2HEox>aJH%aS#ns_Zlkx1}YAsLFT=OihqELgJ_WYk5E1` z4Kn{TRQ(sIJctIV{|XiV1{DX<;o*$bvw%Ub_Ujc>o3z?NcLyQ1LJ$qy2)Q|=3H zwz{V)u6P>K_Jg<=RMx@GZQFZo+x%lG?e;GW*lf&2AO8znQg^_RCO#GlS7 zJ-2AuzJns?`i)oJE@AyTA*4C+nVX)LN9v?a4uAQcBDojTmVldEv5WB--v;4dHz!Pf z82l$NK6LUjN0$ReF6Q@E9ErNTX+dnRU0iW~rSpRoa-04t+A?>|>S{}zlR0yP)qOsT zdxem;8^pb!J_Fp`zL!gj9Xx`zY+o^Pfs*UY&FTk?H#%mc9i@?gh1x;pQ5NPMaF}E^}|7^!(3;A~7MU zH+58Br&`D#II*PJU|!SV=hAC8@p4(JRdr3;w4>);lGcI)DM`9aiz~Sz|F<^6pstyu5(b^A>v-%G}NuR*U$c>M6AvgYRi_AJlXL%k|RE8Z!Ayc zEQLLq7jJPdO1regCgpmYmEMO0dA^Nzyf!~GTNpiA>}i1}lDVL>JJQJ6>@e-5vO&FlAF(=SyLcZi(vh0*W+pRJ+V9mj ztHhopmX%CzD)fG2?ksHk?q5``7CL@`PJPyLbxP1X@ki{msmg#R3%()Lg zZIYS1^AdC2h6=lndwSd+Y5uune`UdX27$C`^J@O=58Sw@m}%P0rDo?MH8-xGe(h^4 zlDT{^$AbEgODC}W2=ZR6Bz)?6mbRu^E-VLr%Ru4 zJ^T8|8;*lDR_*)$>+7GIu%W|!-jb9xNan)EO+d!8ba~C4kic;A|Mh>&4`Z_LH}I_G zo$Z{rfA)l{oEPpVJ~?DxmY%?M_US7r_O(a#A4dynzA^9V(O%v)fw!XT`&He;Nan)E zUBKo>oKgF-BQU6cTHq4jUtjVBt=c|cQb?N->Ty$6jD7a2Bm0G$SMxMiZ|`Sfd?O@V zZpD2vuEw5A^ViS4eOvRks4hk_7c^c2G82SZns*+5@o(-4ixp}e3=CP>QWMsObau8) ze)4Y@)7fj^YdWf~|F)m=Ti$(@`iV0t94z%#0ozXqY?;_2Yg3eO{nF+!yezU4B{ObsqaCyh8{ZHr1l$|tIdvX8J@^gM( zACx(MD+&oMtK^((H|fMYevY5Vj2e@Nio4fl$mMd3Q>Rkn_aKh&O2`kN#%){PVq*D@$F6H=~=LCpkFEVfcr zuRhhho-%30%Y>Esin~mH1~>3E9Dlf;jblegyyhw)&gn|)b~bo&{o$;;6U0B!NTaTe z=lMFT+Hd{S9!>I=MG6PdxE#n#5N5f$VUnO~mEHeEw*%iVmn!F%U+H15s_?sJXT-(z z0wUXg-rme6^Xt;CeyG$=qa7dE~JHdn20uDT!7 zOpSNvYB)W0Uj{y1R{rrz?U$z$OCKDZv9D@v2v^pG0~&P|yCp0eHYDFK?M{qp?s<@j;N8Ak1=~-L3Ol(dx+uFP{_FaoOoNd6En7hu#8J#bm`@xl3G6 z|9>2^{N~FE7w^n*%+pIPy4TONzIma}-tDO&`&$#-bwOhWuy_NFGlI+nVU`z>#XGm0 zPRTuabN$&61=R|V^}7A);pb3{#WNKyL&8b)~0y%#D!30d#I7)K~_V%S?s!f?p3Y zZg)cdgIw4yD|UhkAr?JNbZ$`844AuR9;ZX=hqFnf18sdmG#B zfdR}$2K#A5V4ua?GgoKbJ^|EcFo!58FpUQ#qs-5hhqL&a&r2v zaUM1|Jaiq_SevA6w4CG|_gLxGiY4sJw`R$;c^69FkNYQfB5r-n<-Y--F$GxoDk7U3 zT~PQcJ;l{6_={~aM(rcFR)nB(+ z?b-5F@;u++r3%+VE3Wfx-&J|H;NSF$?~|LCltp;n6!bf`1(dd6?nU0$&9ZLprNw@y z3U4mc+_=7RaiOD9_S=n9JtHl>FKS(hU%7Nk#(V!6l?y&j=5sDT>~H?C`Q?u1Nx4&Z z{BDZN%1ip$gj^qk#`9rLW?7|l{lA2ZK+qJ1d95A7(p6_ad|~-~$}j9q_2;GQFX^@y zn%#Wxi(PNsuM=<04saQXPdik!-ml&}N~Cqxo!8~-9gxzQD$Gz&I-9#f^{$w9&h1|l zIptKka$j7EJbk)ANB-NsJHJlck9_pSOXi-v<0KEkT`O9cE;TdFo#(BWTxrR8J@Z+F z&BiEE&=?0Jw0{ALLzvMlXQe_qY-ImSS}|Dl8?YSv^xZb(*OMZ@%a=EI9QRP~OLCY~ zc{#K=K)U+=-sHT_Nh*ild#zdSa81dp?)c?!K`YRHcbK{AFhfD^eg654{)#r+>lgk7 z^<4E{R28PMzaV76^v_q98U)`yp8lx1S^GmN>rnwA7R5Djl7HD0Pu*iQE@JV?&5BsV zySry4QaFIdtzbs8uw0xSvHkAFr~H{aQes`xF4^nt`r>lZqH@ufsK0ks9R85SxXtr~ z{?1zejHpBVPker#Y`^LM%N<exEb+qROT> z=N&YkihSKMSwG;Hg5AvZj#9Z+u?$6@klYIzw}TnYl453;^K<7}lcPVooYt_~&RHwc z&@jW+R$!~GZ)#%xqx1WGZpt)600u~OsAOoS8rELAGsu0^O$Cm{!>lj95}DD zlsjAynS%fDYj^Da^`8ltyxaii|KVI1@TRwZCwa(Kh zjprV#TS?-dBT@d5r92yMJ+&}cJ*me>@Nv@LGLJ(~@{?Q79&F}Zj^tiY8xU$N14~Ty zM4lVhgEZa*?A4#X@)G}cgFxxcLL90&&suj*wr#F7;4PjOYOZq0=yT7XWBgBUolU#F zaGKup{J(#T?*?(+1C8Co!q*UHC@7t+TDp11i%TM}SJ@fHJ-EEaVB37Z7J&mIeL_?A zi8Jcn;hlQ#)4E)%b^g`QbWJqHn8lYLP!&zgvybY^nC7?TS7;oPdqH(G%xD(59XiKf zX|L*8FfYS3XIZ=OaSPTB;(A=fSMctd;x{X?)2(N7P41ln?t3c~vJdV5c3Q-e zHN0NZvNDT#gBOyypuQ*E+=Y?FlX#Dqa5zoQxx0T8=fV3I0_==$$^;&oDI?jhxj{9C zv(0yPkHp;22AyKNyxntba7qc3A!4lB)xqK@AHBP#FX__qc2Nr&AukxjV1C zoAi$Jb|#07y+nn{#oKR|beJWIX(=ChDAczs`cYqf@1!)fnG*c3PtJMo@lA01WKn7D z&1@5p+rOqTLqXvyc1K(K`#N^BmfrS>D_*Fc-neDek414!Ve>w+-<+E{MLfK5&*J@c znzAOdzgx-4oM}E?`;RwgXVm?$#ZRudU7q2I6b{IH^jH?p7P48W(){6s!_D@`r6vEK zuh!dD9Qr&ZNM2Xs_N~gQ_+@XDHD5mU>Z!l+{-9r2$QJi~DtT|7cDS5hYhxF{?t)~l zIkI~v|2~#kXP*?dJ=&&+>&pepGC76nR~D<&SBU4GHGFw)isTt?6^#>-H`cezk(gwB z=a^R$YsIN>ZLuAt?K2g*<{d;b*8BUX%tnVeH%&PsE-&Ue%QYj(^VZk9D?>}eZLbO2UtbKGvx3E&C9=60O+Uru zo@KK}-S?H6o{%o!r)2iabcM@Dz0D{7^;+uh`PI~SpPSe7g^OCwI@TF;8@zWagv6f` zoVuY}8h?@do7TwY`g{sseJD$GX~gV=X)}I5 zw@)%!+!kcK|AOX`WjhWXw%q5)IJ|$J<40Dfk!@grn&p-cQr{$qk@R6%Evix_^1BE$`AKB)} zWrp)teb_d=Xk~B1#yfXbI&w&X>ULQ8g33&o(JWj#^Q;Af|4b_~pSk7y<@@n`LL5R> z-WtYNtWqy?EuJQDL?XfeO6HruK<-QPwzoBItv&I*re=AZzHh=i^@vCRkn3YmSi{XV z`tia*sX%bmrCc|aC-0ZfYT4}dI%uN}>*qOlWWLy1?Q~r@d4`hNDi$V>6|Ik#-D!UI z<>8U6oj0rZ8!jq#b5eoKxkAccdzhi1{I+`P`BS%A1)AC?q$e44-MH?#URV0LlbqTA zhb!0u!|v|wj@f8-TqrXoJnegr+y$`};Uxv?imRIE2)%t(==WE0KT>)CwW(l6vpid+ zd{tCQ)ytl*6K6vq%icqPLpVUUC{cbYpzJb{&pTFX2pAxRHzMC?|rem8%)@Qej3+ zu~B0l=cS+0F*}49;tg3f5+`vlEE>LyqQ*pe+~(n9$vt=Cv3y%vL#&K?k2u` zaq8lfdq-InGr4B{TNu1We4j<9hv4VTlZGF+c_4)Ys0@M`%`&~{(2lsC2IB%Bqa~${ zEnQ;D+h+yZZd7=@>g}}5OLn2sO$jwRkF+(W-AD)s$}7I5`o>4=M!)rmv`UGK=Tq+f zKr+`AW+*6p-z~K3_}}+)2kY09F3+E8u*4jBJ##6WYVy5HP3wXKF1#?9?U20G{(*MO zQr(6#CpSEN(tDZHXt~X|Eej6rRx#6VLo(M5*<9|*3r9aj9tjCuFpEuNkZD;>@*3$s6}K`^^ru`o|-(lzH8Rn}IS$9{UR&`X8#VMs8QTBbyu6 zwyJl7rj!ut)8`6y(OZ%Qoc-w!QY3@ee{6-}A!qzE)%x0MuqGPaS3a|6! z#~pXq`k%YMYw0}oM_Y{lrE@)-IbFkJO}EzX$YXP+UaH-nV}JJ11qa6Y4^-SEgE#f8 zUBz|%=?%~v3(UPfAOoS8<-#KS*M1)ti%ecncdX~GYpvSpg}!!qR~{8v3wWlew*QDc zJ8E739)K~_VyeDoKe>rXy zyyT;R1J54L;45Z)@ttQ@Ud)*@4=m5$arm;=%goZ= z>|T%mwHe6u5Ntgb$ZVD;hAF$cmhO+9;jpIYQ)esdBY(%^8Nz<2_GimiZ8)@sYwIH6 zDL;-hZ)P}=S5uzAY;|c7-`uvH_Zbpnb6Ijusjo!}Uw@E+P|VVK(n0odmRa7OGtuek z?)$Fn-4xKCf996dj-?h60_I^K|L^&jR1x>8RW~YP+`)<))okn+mUY~gDh~GSyeLo~`Yj^4B^{H(rT~Nw4kpq-w zVd(+1o(*O+%QBm7xg9~9o_~l6+F*Nr>D|NCzqvc7tXp^9O;*Kuse{?mS622(!H*uL z{mEig3DIX1-&$k#=H7|VS#I78_hjCJ`j{|tgJFh(-0Q&bdFsMDpVUP^^u5nr6t2!C z`=xZo^3zQ#{}yI9&o_G;{AOyQL)Vptr)y;|J8${1=}=;Hr*2u8m9ApZYmKunkmD@` z+1$jr$AUHgFFql!F*AK?-Gld1JDQbuS!yqo_%tgcTSr26RfDO}CFTcPtzVp+bb0#) z>6aIFw0^fOjf-06edo`EWys@Ep~&XWwRlx~z*R`gtF&PK!-fkH>dy+cdCfkXS`w?t z)D^s6>!@Vd9rwlmLl|VA+koYi45D`MUWbYEM& zSADrD>+3Gh>%aYVE+1dA{iKjz&I8f+_o~(( z*S`ID@dt*TW%ms{{vLGTl8c|uyP&2i`0$@InY^*u$-Wx9>Wz1adpVXKlLpO&z`_?4 zCNQH}Y*Zbb&WKfI?+Uz=!xI?(|AlB}R~J)zIe)3>?7PYGJj5+*|#(g zf8MnzbhhdZMTc|JpgCh$IDq=faC1$2nQJ(CJ_t$jMpoIe%(`|vGGF$i98>ln=I=$i4v6z) z+Sr7ie^6i?DxaSFbEjK z$|js!;?XIoc@i2gHV4f<$8u#ujPPl5Yah)rmEx1~#eb)H8U>5(uX|>8DMa^KvsabVBE5B~L$SN$8SaqSRVs`C00R^V-J4Fj09ko8noBuPzQ#bzF zyBeWxS7F)UZD+Slx?hmJ_krx45~qYdr0`7!n+PFTcvRoLXn(l!q|RG2ujImy%WHP6 zni26-DRS1<9==U;1V#E{Q)G=qZI$@XC++!k`1jIp#vP|*c^{nB&CyF-c*6#?Rv4BZ zz?U;YjAdZ?x#ioAI+iCJTi(e(zx(=*_iuqIQx7fasyda=7cnm`eEqIB)>_j)-O99k zr6QnvYtQ|K>wh{3Hns~qIv(z`JyQU=zJRTh1{uu~7d~a-`Md+EuX=ODGhJ>n<(w%9 ztNc7)fI0j9@lfsOLNeW=C+y#I&k12vX0%v6TO}4H&ZhbQ_S09XliXIvd+t4dDwA_{ z#jM~}K}T1fxILrc?NxWJ8?rjrA!}VB?H%wXQy@b@oUc!G zn&q^gA9&Zk=W@BJA6_FS+|gMV6A>Vt_V{%3yDR-IOaZ;YCe8`wt_Q+Iw7%W}%~`<0 z7j#Dt)K~_V1ObIEf< zhx079fjTXPb!+cB@6(>Pij}zoG$#Zz7jy>^%xIPl?S2`YkgMn z;ANMWySNt4><*oAc-QlIHLfQ<3!bq~y~sG-!}Rm+`HRh?3%GwIR<#=D-XE zx%aQu`-is;PBJ^zb+|3J&v@DPUE222T8CFGb=iI|+}yV<+!l62tF-WN+98o6sl5xV z1q*M*&NtXlo>3Bgdy>#HMp)-a=4?zPMdcq?oex4Y%8!_2L3w0mZ)^n2T<_%vU{ zIpvl6g{6Ccs84^m#UZnnVZL<9cG;eFQ!=Ajr0khg6%{#KTP;Cj1F&!at=oi~dtp`b z%97B1+l;u5e%;JcA8WK_@4Bf@E<7&|XT13}?fr(L9*zt~0UNpLpD6EQ@8;RIn3~k5bc_H)Dm*A)kQewGSWKda1Fp$xV4>V;oqNIYaM- zwwqBZ+tu};zBbIgpuQ*E+|TdBRzABeZWaFJNnPZ+zT4NOWsU9H`7Vf7R`8N7#4|?|XIXE>ihW1Tz#A4x;xI7{vaaJlHT} zmEOn4@>{f5dTITfDw9>;*8J~#NrCOE;>OdLH!tRV-EOpBaB;&5!{tpH1|@$S8ZAQ? z`?KnU#-L#C1>L;{Gn(b-%b;w<(2yY8(i`cz1_eaaMv(pbR-al#TEGOqj0dO zKU>|Wo(%jN#=V+|)G7{2WyW#G0)%IxXy3KWhoi7=gmptcm5ne;V!dn~&<3e~3HS#oaiuIY@?3;(i7H>nh)wU}O*$Ya?PR{81O zIrj}6?3~+HUFPjM@xytQM{p3!HeKI-k}^2i9!eTdX?`$-SUD z32v^?r40+-Ejgm}H{xAk*Nk@GnL%eHcSmgbb$iPz?%z{g@3L!U?J=#HmscEd#EP+d zkM74O*VcNSicy}L5w-Vtqynf6gT-3~%urDH#&EV?lz6(%%>9-1`0biM#L5q)At*Qfh6H#|glfBSOt) zF?~Nzxa-S5)#XcgoIlUqe22gHf^?|GA1Y2>XxUkGy?A0w;^TF{Dv#A&+2P~y?tg6d_NGo-V};zG>mMM6FQ^WMoBRI! z`B>Y6DVq*A+8o}zTY2@3;`83u`QJSMy~k*Y`;OWYL$36+l^@p#d@-D*Fv+3!R!?Z{ z)vZCBKIg8Pk`%r2*gPb2Vf&0gX0v4Z2b`W_-P`spnqBVn#LRuXT?^RlQ#!moHcw(p zNjt^-Mw(MJ?5=$3TCH`Ljeq5>RW)F*onj+a+4$h#4&9vH|B=kC1sMp%EFIsxUOrRK z7s@xzIrlgNZ&XxHOiY~RTru;M3i<%BTcwqbadsWEiZB6}mR;P#5 z1kX=)XH~cg>bt_iw+?D1h+@%QoV-0C@tg|jpZsKBO0nZro4YxO zeCF%TJyqo(b?^GWoAr)e+kM(wUe&lSoA%%Qd4_WzGJ!jKeWQM}97AYS(`{?HB&u|8`{mqOAfD2GFB|<3E$EE=_W&tj^EP0SF&2)Q=-n_S+3-HN7W;2;ukAj_kFw> zdp8^FUUWq6Uo^oC1-X~AoyBy0>(--OdKrnU)peJi%vrsK_5LZZ-idD{ZcK8B%YG1@ zpP$|b! zn|69(oLgQ-Y`^`h?6)z0f}7g{?_VlhkiANf_8Dlb9d0gT*oV7LW!2YSz3#r0sqZk` z{c&~htxwaAtu%Qvv;3BlvgW*f&QmPnRbF@ZUzz`XntzCaNY#e>LjE=eKl`h9|=A;-!hg+hc=dwE11^fJ8WUGeWd^+Wdj zw~iAxcBNl7ww|7UrC$B{`C|gR%0X-4Vdl2O3OmeEa{vO!ZvNv|0-xKxM zK=Wu7F|DU)tq;M7ozJ# z=bZi+>0bXsiT$Y&Q@7RW^L!hdKxx2}o>yKK zGVPOf&(ZXwE`rpPBo@XZzb;|Ccse zk+07E&BI8iur2=#J!fv>^1bHZQ2BuKx!W;iwI$b&q@2|$ai9D%-B_3FvG#)Bi_;dJ z*v351$HcwJ>mv78hwK^d&UAMPX7lyFHf1Uu|1PE zu-@RUWos*Y&OPDI--0B&-;$X;N(#Iy)`p!mXEdono+ktKX<;l!dF_8qu4J8x42$uP(A-UIKgN- z$3qXL!$P+3FTXfo;njq$yFQt{BIbGJl_pkTL3ef<qh<} z&?O$Bu;lq)u4wlhUQo-Hs)W=o0kv5{W`Zz_R{AOZvR~3q!uR~=z4q(7-`W)YUrb`0 z-%MMNY+L;2&)%1st3sF?_Y{OiEr`7oT)UmaPW;rA`r02WjeaQOn7@K>AM!wYB2fn7sZn|bI zhuOq}G)^R(`@AlU+1SA~rsA<}W>|Vi!dthwrPDk3GWPbw9-p;h0`k1bOpt+4%wj9r zD)(f{-3MAARnR;kO8y}M0q+}e@d3%dUuWF`o+bPAkT zS;`;c?ELoPdFxY8a$cTauJEbxz|pN4FDsLsn)Su(v#uA5p8ja9;`&7O#jAri^8N+P zy47?x>-xmJ(sRdNBc+EqAOoS8g=LeuUuqF7vNeDvn0 z&RKX&R`S7D#zYro=>&GsWp#TurL+~Ty?=jAa${(ipTv({6T)cBCpF1 zMKwpnJ<^|Up2v-qrD$A2qbE1M#H;Nve&P3^!P zm6hw`&ZrgmCi*6XCHg#FyUgJFpP9>->GAgcPP%>D+&zs4DIDg541{8q_wVP5Nyi+o z{dw;})anC=-JA1{h_7Wg*qzF}aHa{zoI=@`TUN*3XgO`LUfS>G<5r21Eej)krf;8Y ze@f<0mAf=(FFee>puJB}V;NW$S4j0HCc1V2z>MO&|U9j?YOUy*Y zs*s&;{&Wbdop9SRcgGKfCq}!(?GNjxB=5f#^4&n;eJJy~J$CN}C%pT2A@7&R7Qbp8 zTdpZ4uOoJV@)*p$i(rO=%Jo$f8um)oZo1tk_q}UV-SN4H0{8tZxwc5EV9kXo@BiuZ z#C(6Nb(`ta{)YSe?wkBEetv6KAKRf=J}rfql-eJ!KY-@sVdjG7P+>;12y1fse=*fE zxmI*5v!OwuR(Z>orFp3y4;xN>edjfKSF-90_V;(3gr+{VUOClxVZl)!#>+fEXA|}$Z1d+0l!?64R={6aWp*bz zb_&zHl0{c`8vbZ_(R%r**xYTmSsipb;!>svA@VwUVQxigZD;Z^Bn8l z=X;-SS=eQ{y0P%>HCrndwm1eI(ApYU_=4JMFr!)e%dhHjUtTSWe9sZDaJ0>wb5Rne+Wrz!d{Ey*uL9{axQ0$w!55O}u3j6`Rx@GEYu@Uc2M*n(y5= zKZ|Rx`Lg-=9If;5YqY!7e%?jyXMxr7>UzCS%WiM0?+&})GjGs; zG$q<4q@4XX&)h;SU*TMaTIIPr|Mg51jB45En!NSvLciylVXj+$?E&p+fw>nn1`0R# z=(a=h)14Cif~rl{sa>AUEvHOgfsXP&ugeshsM71mK|uP*cCb-v@eW3&Gy zE6H*veP`zXea_P5`*!4Z)M}Wapm3NbUVk9Id-8quw3b8LLbEff6}TI6PYexlqE0ld)AQ}e($DPD^m^`O#LkV0eM^*Gj&b&Ax1*VeXwEzOTz}vIY>rGnS0#fy-nR~BC@35bNWCcZ?dB}K^((Y- z`;iPqfj?g&_E=tEv@qL0!ShqK#-*pr{GNa8-P4yN(Coms_<#Fhv#*YqW!-+>JF54j zBMUTU28%b)+&9c5V^!7snPNwo_NG5w^u{! z`^kb2z31Nh9TaXAcs^-vv-YjhU5hUMFnqTz|E#5ziALV>gi`COSzc+jXHG5CGZ6oE z3(4G#$mTwa6FoVl=Eb)9DK?S5a*J2&XAtq7bfMAP;O{QR9dC34n2(evvVS*`^k)C9 z`#12zqPurKe|Q$vq1UL06zMz`|h@vbko@dOg?8pR(kq>XI4GMj4a+IL{h= z<=~qoCH-^lr)zh9XfZ!F(Y3ah^OQ>i69f(PF1Q>?N*1v!HRQeSLF zHrLI=-)`>abqfwS@PF&Mk$&zvKU4IthvkV%^KQNGt6-j`ue>2|vV`uNqrZ3FVc1>s z;EK7Xt*`d^($K5wvsQhxKwc-d1=-wu>3iX=HT$PYKPqSSwS8@?A<6Q}X_EP=%X%82 zp`Hi2n?po=c&pBzEb;z+Ug_zo+S%8hpKw%;{JAsAA#LmT)1a|9SU7A&Hdj@_*+}7( zw~6RH_KQ8wUE>(zCxuN{ZrVF%W5pNlg|{QqSW~ApD&&b4^> z9XM=%)YSJga(f-LR~BY8OWE(H%DQ;&>){PE|89T!Gwb@ZMh4@6r>neUcRt7{nZ07q zf#szapIx8$*{f4%-<8xw|5e`a*>HSWkN0|?!lSQML38pj_il$73QA|Y?uGuapKsOL z-;mhbfA*M^W}Y z#?1aHc19mE53RBkpSNO$kz~(7{bvG2FW-hQoObus^p!54 zF$b7?cOjd*_nAa1+dlpfMs3UYt&Sh_kKZ~L^&zMxr@}y*X`ba9Ujv;RD{gL{q*ZZ) zPgwb9N5ks1g=-Ejbn;J%jkFZE5CE-Nhnc$@+1#c3&m^V3*}`eGQLymSj9%qvH<2qG zBE0V>_#Ru`sK*@c`ik-8|JM)FRx?RVTe)AK_b&gA#TyIut?pMndGoE95pw;u2iaUn z-j+zIfbjD#e(=~Nt&UHfeBV@bW|G^Z691HEyHfuNY~OeBIP1q{H`*V)S~~UTDP}3J zE1DAwg^ONnpDD9ZG7)*7CFp!8n9(fu2a=hNzE9Pks$j}-J;I%nQU86L@at_N-IF{; zu6C^0yZzaLwB9WDAR?zOWq0Z)&b3x!omTx#{+II*ZV5Z zyN#!BHksO#bu0DFT3fO|cX42d{-qH0Dal3GSFwDbZ+pUj0)z4GH3yp}@Rb%U3u-QY z_jk$3P{aB9zZQemhQZ7Qt=WZ}d;K@V!#mn%1dJ-Z1YY$-wG=H|JNwO?4GSKMe~d13 zmB^ehQ8~aS*gO2f6oC-_>+&v%g_jr4syccj(l|Ko+x!E_`4}|k05{i>@t4U4zTmfO zqgeEK6(m?<_D$pwZTaNQ?qdFt=`mMgNXkSBE{#?1?z|7)Z=u5^eyj7d^$P7@Ol)!Y zjkxa0fc7}S+zT2rfSWs`Pi)=9H#tf&2km3T8zcAaH*s|D+ILhZ=u`7T`RV_+9(Sm( zIoj|ip)BDIbME=XwQ2Vcv8|n;?wvRx$w&6r9MHNpm|3Ir&<_F7`qJXm+_Kan1_oU= z@cDS4A%&suu~8q;Ed)SmC^s_~ybLHgCsQvAc>zQ1Ze?O_4#OsS1_nOpnbx$jZ`52w2!O)AC^NSxF^z#CDKl@hu0RA0 z*(^|)CMM?>fz}QBv4zlzNa$l7e0|OTWLqm%e1B1XIb066OKdNyw1fU@Rn(qfO zVKj(MDh7>Zk*a4@{b&e`hQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mk zz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeDjE2By2#kinXb6mkz-S1JhQMeD3`__# zK({;{fNoLZvMDY~*2~N*E=kPE(aXtC&Q8nBN!1G}N=>y>Ffvdm&a6t!Pt#E_QAo_m zOwY?tgJ0qK?>_{9`~ljp2U<%7yGItr2dzVd-PH=(dk5l!)WG)fgZ8(9^nuoJ!S3z> z?M(xz1L=e8v14EW?f(L)1MSfUofi$V6XX$)80cL<`#?c)4vJ&YUPw^9g6@t2oxKN&SJ2tZ1`G@gpnHixcL;&* zFfd3nFff45RRrDh2fC9Fw5J_(E*a=dPtbWYptJ!> z3!uIEpm+hL7fw(-FfcH{;vaNJD=0oW85tOO7#SEC85tNrXL|i%U|{&pz`y|7XZ(|a zfdO=H4d^Z|&^?Q985kJeF)%PZXJBA>!N9-(Ix`1!E;8s&mvamZ44}Q=XBZe5PBSnt zoMK>L0Hrxl5&)$iP#OoNX;9h(@?LRT>` zFo4b;1Z@NYo$U=eYZi3R3+Vn+(A}!Mj0_BXj0_C_7#JA-GcYiK?&SvU0|o89{>;F@ z@PUDW;XMNb1L#bUs|*Yb*BBTWE-)}KfYL4~t%B|;NMm4N0G;0pI%^kn-x}z=2+;j= z{GbHM$iM(Pm+c_~0|RKE;zb4q2GE*l(7C3d^LIe!(t^%ixxv7|06HJ<4g&)N=seL& z3=9mQbId^7mOy88g3eL|<#kYA6lGvw0G$B?%5R{(yP*3|KzRz3hd^hPf$qixiGlJJ zhz6a>1foIM0~DAH3=9)Mjt9jjfqVtZhM+J9o!brCzX?hYpm+y)4V33Wg&inufYJyk zuYvL%DF1=-At?WW(hMlQfbLQOr6*9hgWLs5H=yu``3IEdKxq$@20>{NlqNxG6O=|l zX%&=aL3fmb`~dPF$e*Az3W`fmScBLg3_1@Tw67aA7(cwzk~b;vKwR<$ZSyffWixu|6%rl$^%e- z2jy!}eh1}yP<{vHbx@v%<$sVENFJ2;L3tj;2IYTHIRIkAFiZ@TjzM>MfZPdlAIL2r z_k-L5N?RcRgYpn4OhM{EZUgxT6n3EV_(AGHVFfz#9~3SiJs|Zkc@Q6z9zbCPI$s`? zpF#VfL1iPTj0BaJpmGybu7b)}P`L{#lR@P*sO$!n;h?e{l83;aS$IwgYquuoMuq|1?5#xJ_Vi23bG4yekdqUg3kK{ zoy`f_zzRB>vWJ0z0hDJ!=ShOfEzmiFpt1`T#~`5LQ z8K@3})n}kO3>5w#_krRB#0G^uD2zej3#!vV;R=!m)p4M@4OGX0%4m>VL1o7QXn8Z` zDX7%|s?Vd1%wkI|>=ca5;B~qoq6q~`C!oC<^Hm#kv|Ce?1Q_E?^$he37{IL&&~B3I zH|b|PgkCFgFy$ncq!yPjFr-5FsqGADs9rCy;s*y~oQ0mLiJk#NYDGa&YH=}xuGcZw zLcM%BCdN1uJwppUBXAwa!@$tMVOyI%>Ex45OpI}sdIow%3=FyXMd^uo3=G#Q^VQWa z)NwH}#u@4vf}D_BSzMT7#K2$@-+%PS>nEa2jB&<#W_rd93@NF3g{7$s4DaWpl8a!0Lot=8IOH2%mpVC%%E;FVqm~xkGY~(Ul3Zi{>>GxP{Q56TM3KA5R#>1>g{!%M5N$HXKgp z_SHXQXEU@=r=?QYpx*ea#Fe52a2WV`4PYGt&c=k144IpsdGW(zn5Bk3~)a zRF44z!*XVbJr=I1i;|edqro!9ps?NqwP&~CqlW@c9QI6%b%u~QO-ez!Nh2&X9#itLoO)OGB7YK*>dFS*6aNqAQylGl)-`(Vvpi&{W$eF#pg_nrh0~; z1j3M24$&mNeBtx?-_1q9>A(mib&!>TfuDh)Ve97&AExXU`vgvJV7Fahg@jTD>+kty zFH2X0^%#KS=K)k^HE(xQ`@?X3upR?F69$G4P}8a;pV=F8%0wab{AXog5Mf|wSa5My z!9~8jKByiu1_og^hzm4lZqU@XV6=wH7&0&@u|fQkulu?4^}(+fp)v*xSi%6DdJP#E zV7u`}J)eg0PEvWy#29C;X9{u~mK0(J$)}ZxIXRidB@CaYZ@4B^qX-EDaFAw|gGyKi zhA_3RS6Z!!cfn~Cl&G+TH8|HBGB8ZxWMGhDU}z|_pHyY{Gk^sYbKs=Iu#OX4CN$)U z>)ZU?@zo3}W5|HVG|V`K*^`)@oLW#)%y8uZyUX*RJEnlsj;#h)Y3 zrg>%*$R0@A*}w$}QJqs)->khL>b|*&d<-zOl4r0e0j+e!{E295Ivwa$1N^M+L^sad%IobMk|m#26~_t z0V(+hl;uFCz2ag3@5|A5*! zT-=aw^PRcR{4rP0Iz*lm`LtTT&$ z3w8lGJ7SgxMta~x#ZZt~RGeB`lv5q>t6f?yTAeza9&W;X4mMjK0W;XAt($C zO+hkYP*<1Ry-z)mGeHj|0}1QmlEjkI;?&}Y(ucmZWHWw*gea)}SyEY$s+(ENP#ph9 zcEXPiN3h$>^b9Q+QlPOA_VAs_S~tC^Ak!dWU6xn`YR)hSJh+{5SPwX?MR*`F zmlV0Yal^BI3y`b9#T$b>4t`mUB@QVG2`q1;B7F^4KOW0omkdn_n z?o-(>=lN@(G6oC`%z}_I{>^&Zg(6!+HJ~y^3=9H-kdnf+(rMQ2gRZ<_8B0)LFNNxv zJ@b@QVeSrYunahtY!ZanBP;W_e&VXAU^Gk;-7o8qd> zfjhxljx0gQ;5IEiAJR(uYae)WN5=G-U_D@;CJQq#NH8!oEaIE7v$S`&15^fASc6+n zSnV+cmDgBh!2Th}1^ptB_^JMK&9Z)VK?JB2gXHZQP?_hep9be`nkfQGRhFP?jsbTW zLQZ*rJ2&Df-*A@krFjLJ1*r@SeUYzcR%qzX2A9|1w1d0;NvbSK1yyJ(PM99?-SsS& ziP0F^y2af>!5s#;b3N{I6n85Cvu9+WXQF3h!hogi53B2O*Um+W<+@4vDU}r_F?9{w zRhvNNjwv`!ao3HQAqwh9fLc$u!;KvO;I;>|pT#W!Dc5k+w8kb32PGhNa1m?vvlk8% zet^<7xO>XrDapVf$H34~yeD+^Iwt;);I=d6bD$w3=}*K zl8{zsYTe87qR5BK!6gznH%^d**dxXoDeEWOe3FR~+TCWDB?%s3Zm{($sFZCzWCQMN z8XD>u8gGTVz{=1&M5^&Vtn4*pV7LsmXTQ$uTh^fxUf`0#2-Na^EXlwi&cM*HNmAbX zps+GKRK|dTAu$Uy{=mTS)qe_~!yPY2CdN23Jwqcs6NcoB#2nE0%=yQS20mgjH^BY@ z=Y?OAkg_+RE@#i3jhjI(Fo*OGG1GONiJmd2KbMl9TvC*om)aGo>85+(@H=P^%z%M` zRSHsC_AT5L`nUFcD8e2|DM&oco~g3Wck=Qt;64+$d{dQzl)cZ_RZa|lRQMfinhB_U zOD!%aN=;>8SSIe8CAOyn)JM0}1II#Yabam{Q6CZE*Sz+&*21328mr6A!puWh&Uioblzpn8lL7}9g{lM-`^8T9t;&~q}< zxCC;Wv7Vu&oZY4Dw^XIno=Mh`R~!vLBoD@tWxcolT)DKQ&r zk3K&~lKjNYuOMj$)Yr@`)&*I`P~MXEOwYg66cN^CQjpqNr2E#R&@)Guf8)btS94Y? zPf$t(H3!Y`xee2vI;cIk{Uam?N%4{U-?7fQ_ZHHA19#{$|Jn)Kd?5C3t!TtfK z>oPfre@^z+@QE{BtpJsl;HQ?-(>^88R@eP=M&+n)~op3;SbZ zunf5MbV(7C;tvX0e~tH zZSt$YGDe`Gx$M+RaN+y!h0>SrC8D4stTqc6k+ffyW+gP7ZHOrf^jt3DxnR#hBnduoNqDyrSy?%7z3RK2~fdNZT z0@NL1U|_(J8x0v4^7E2G!}km)HaN|kQIW_6HVxc$y`Ta~qeoA_+FBRyQwWv;hiG|G zVnIPA1H+LyH_xfpl>KI61ofyvR^c`cw;m%kh);7h<&suSN%9A^xFG2*SsfA!*|t+U z3NPOQr327VBdDH(o}_g1dwD6v zp&+p&LpM9M@>22se^-NSIKgQLTwZ5uLGreP==~X1`#C_V7gUBAFff#7WF~_~n9k(( zeP8VV$qk$v!9ki?oLT{j?iD)Ww}ORUU5CmTFfiC@Lwb5#FT-W+7xerB+XIg6@7j>~ z@zX!FNb=NS(5S8nWDK842U3Q(9!^d^tNytNntBbuy*AynoW%6WcQSVIM%+FHt{1?j zozj8WlT@f;>%h$q>SuvmZOFhNsSAniixI0Xcz&7x6x>=j1l4mm>TN>?hCW?LJhj+g zk@mW9!X4qC6}pgi$juJ5e|FIwpl}0~mWB)rrA3*#pwlnac^i(!Xm^213KKnWJEWjA zuQEA5B~{ZdsneW~H5P0dIINTOAuW`%Jib?=w!YT{wUrG)Qw0oZiNz(LxvsKDUY8C0 zdO>;&^}vG;MX9C5nQ4^_{3@;?_EU9Yp!vsyfkD{-lC!!~rfl4E>*RfqY2b+m24e$A z8>yXLbDrGcmIq)b;BG-;H4U7zaJ144@U?_+w|8b6LBinPw$=Q}K}#2aQzE!_#@%+o z>K||hT5bf1Isdg6%ims?1htVs9Xdk>1`lIMN?ffNvFKOpe-&_ug41ZIF~kLLek9$E z7v?ZQq~6EIkn-R)kAft3lPah^Y7D7=u$C_1ax@if7EXZipb4Zm@Zc$^%w%9t09||x zUVDJNvI;WCpyygql3J9Pm=g?YaHrcT7$T+zEfCX#3?;>&*;odK1HVO<7EPHTl9ZXJ z4-(_Qx7g;LY~ikw;^Nezvecs3%LB0K<^e+Ai>pO>x+8m}j06C&F5b@dS(UGQ`+D46YR6+q*#MhZ3x z;A~W!n3h_kn^c;XmRf`-Ad!`UVgX5)F4#Pf*?9D0q%VD4eGD;hVlV`mlv%7>P?TSg zT2xYr$513yAm1YN=z@kwj6eqA(VYUGDA(83hcLk2Fd(1@G9eGjKj4{pP`Uvn!; zq{QOPWZlHll8pR3-L%ZS%;JpHl>9v1jMPL>PQl|2gd%-ieFTRnKOor!(G0U09F&B7 z0~k?n@HT zl?zV|h6L*hq#yvf4ie&^0MP}tpuy<@pU)ubGm9bNi7){s0(LLarXa->vQbFFVDrof z_#f)0%wkAFKsE{@q6;d@2~`bnQ=rKU*%+u0*fl1^x(1qTknm|Za^{yDxwSS z_YrVSRyinfWR(}|8iRTi1XLpx1)wCKn3jsj zxSa(mBf*tDHa9~I2l)$69)T`o133?}qz#m_EcC!7WMWZKVkM{>T2hjl2b$^71&>}8 zCFkdrq~?|AreqcuXMTvRosZi&D+vU2TIpxgc;?$CYqWqG4kUR3zz~k7uU_OSDVd4jE^>y`;c;FZU z>n_R9Oi9fv$xO?HWLq2^Q)FfOy86gmqVfoA92V3MfG+L;yMa(;243@`ud5HHbwM3U zLU|dqeg;(bl_aL?8tPdPY%C)cVIXbA@B$6&FG7_zyd(tag_Vk6orK&8o$UrUyTR*+ z!087ZC!mPJ5f9+vy0j=qUsoT-)CD!V2)VNmR6G?H>q6F{feM7;%#>8!i=mFC{&-L^m@JG}2It^N^7o;Ya;IRW$A*iDS zZal+ug0eDbfC_)!K(QL!dqr{zIBdZ&0vkU8J2xW{+$Sx`2Qi8(bCdFO@DciP!o#1+?IJE>m7^Rz&nwMUZfyWbARD)6zrWxQM0Xr=vH7zqQRW~KEBoQ&# z0Esa0zyorl0*}qm0Km|K2oDS?uqVN8z+*0GH4!M8Aiz?mP@mxwUHV?Uzh z1S$*Q%_wl9GzVqywEUv-#G;hcl*|H9`$`XQHxsS~R6xVj>l*4=5-dOA)_@W>%t~DY zB5H5cmLSL%potxL(-2f4>w$fOClsJ-eLoe9w4iFK#flD!Yr_zg!G`d#zFZLydWIWKnI&csM`uL2ca8OyArk=(oq1l zO2Il2T(F(sq5)6H!+I4+9StO7kVL_CJ-BkgV;WKq2GnoLPpO1dZ6JX{Q3~%wxHYsABfil&88WF-Hbeni_iU_m31JOgJ@k)9Y-kTqIBT& zb@fsB;IvPu+=A|20O^J7V*s}wz@3%6)b#w4%*2v>d_7@=BG6DhTnAzV8#J;7nc%@+ zi}jQ9^U^ZYb-`hWCY76?Qks*ho0(gXUsRG>-t?TvAk;4C)A{=qBgn=cOhWl zB2Yg8u7ju=0bv!WaZpl}39%PEbO}z^AXh*F6^{!Lia?1xuQ(H;Ll@K?1e=0KFT&fP zW+*%ibPe<@Aw!!5MX8{{Vcnc!hs4CzLk>V=f;;Mhz` z%t_2kPEFAT=iB7`yyV0Z-Q2_iJibLJg4DK99pE`yu$g%DqFM)X8H)YjYM)TGgA6x+Gn&_Dk>;r)cE`r4xXzaH*KaZd~u#3UI231wi6%_dEDySl`4d4qHL1U_V zgi;4=aUiG|g02$;=V7qjctRYtNC&ksigm$_Wk?xM)aX5QDIr8NxC#Ny%P z6(t}s{BD3Mf*1kifM;O|x*t(Hfixp{MCHuP^t}9{RFFn6L)VmuK}dMT2H^_!M}lmG*6bj6=j10r zdEg3rF3E?ie8FP? zk}A-ICqfT6nSu?%qZ_@M29AfEROFT$*!$qj0$L&qY0cp?F(fI!I)mhQ~;Kns0J(yP*n^@>sxb3h3Kgmp8EbrX|{^K(i|@HP6- zm4YG#l!%aYBT8L7K>(Tv1~t6$l2eJwy`bh+K_#fk0A7C!wu6wjVT*-9B@A@kFjya< zb_P<&fZ_)cPGH@HA_%k;T3=TmMCqD@uCE1>*5*rX_^m;$FS zJi0+iuOt;T%K>G9(~~90dVIP-ZBKA}3fw0Fc^Fj$oY}zD2_93Rt9!wHJaFF)T>TN& zRS4S00@ej)fu^w_l`UweZDLt!Q8B*6h)@JJqNE6-gQ$uGvThTs546oj7reCw6jtB` z(Yjg16?nZ1x&&Jnxo-zH2}4xZSPwLJj=y$*+X!ktfEIUyYzKP+oWkS4>p2u^Y82u? zdy@>nEYOlQ15m35tQn6}!SZl}EkL`|!KUKT0`0kid$9;_fw{V%;sdNdwYVrXxs*`+ zK-GXvfwGBOOn|L(4K@TkX#?s#=A}bAdj&*QT(CV`pmA8}_APM9NhmX;E|>yEC86#H z@)8+vK;a2!q|pRWYaKDRpbLs^ysmw_&E-ozqjT;r~Cg7eNgfP<;iiI*Y+W zR=SYlAv3KKBAA?!nwzK#s~qq+46-=_G(->HC828qTU&%j4{X~6s2v2|K>=Rz2@VcC z`mi|-R_7O^x)?k{L&!Yvs1n3bv_OE>=vXDey*xtc564(2C^4fAkAjDB2pR_}w2+2c zK@Nlrx`MTX2dI&UV~dj$^HMVLjZ}j?UkEY=v>yeOjz9x|`0HHg1{F{{8nRUd+_oc> z{IEC+WNon?)Q#Za1ka41s4Ph=DJsnab@>u=a_~;VBP#>B4M`U`aKK>;Ug)ozo0(jc zUy@jyjVG0%snpliM-u@X3a)7In1b#XsFzC;OTalF>|cD^QOD$SQ3u{Z<7ucO;Futk z-qA+$kq7$0r3zx)AJoSKZ8itB?a*{Vmm@&(8}U9s(FW?oKo&7Ti~$D;LDxf?So&F@ z;fSnaq!|%>u7GNSP({h$_7JGWS(FS;ci?D*%xCC=SJ8mV!2~KvBz5S5J7Mq?0^x!yG_cdKbt&v@!F>ummcjQpfrf@* p`<);`22OjR1}SW-6&_ { + const token = req.headers.authorization; + if (!token) { + return res.status(401).json({ message: 'No auth token provided' }); + } + + try { + const decoded = jwt.verify(token, process.env.SECRET); + if (!decoded) { + return res.status(401).json({ message: 'Invalid auth token' }); + } + const rows = await database.execute('SELECT * FROM users WHERE username = ?', [decoded.username]); + if (rows.length === 0) res.status(401).json({ error: 'Unauthorized' }); + const isPasswordValid = await Bun.password.verify(decoded.password, rows[0].password); + if (!isPasswordValid) res.status(401).json({ error: 'Unauthorized' }); + next(); + } catch (error) { + return res.status(401).json({ message: 'Invalid auth token' }); + } +}; + +computerRouter.use(authMiddleware); +app.use('/computer', computerRouter); +app.use('/user', userRouter); + +// Start the server +app.listen(3000, () => { + console.log('Server is running on port 3000'); +}); \ No newline at end of file diff --git a/jsconfig.json b/jsconfig.json new file mode 100644 index 0000000..7556e1d --- /dev/null +++ b/jsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "module": "esnext", + "target": "esnext", + "moduleResolution": "bundler", + "moduleDetection": "force", + "allowImportingTsExtensions": true, + "noEmit": true, + "composite": true, + "strict": true, + "downlevelIteration": true, + "skipLibCheck": true, + "jsx": "react-jsx", + "allowSyntheticDefaultImports": true, + "forceConsistentCasingInFileNames": true, + "allowJs": true, + "types": [ + "bun-types" // add Bun global + ] + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..c5ac28b --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "name": "inventory", + "module": "index.js", + "type": "module", + "devDependencies": { + "bun-types": "latest", + "eslint": "^8.54.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "express": "^4.18.2", + "jsonwebtoken": "^9.0.2", + "morgan": "^1.10.0", + "mysql2": "^3.6.3" + } +} \ No newline at end of file diff --git a/public/c/index.html b/public/c/index.html new file mode 100644 index 0000000..1da48e5 --- /dev/null +++ b/public/c/index.html @@ -0,0 +1,354 @@ + + + + Computer Inventory + + + +

Computer Inventory

+
+
+ + + + + + + + + +
IDBrandModelStatusStateActions
+
+
+ + + + + +
+
+ + + + diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..88fc70e --- /dev/null +++ b/public/index.html @@ -0,0 +1,170 @@ + + + + + + Login Page + + + +
+
+

Login

+ +

+ +

+ +
+ +
+ + + +