From d4b3b74b5f6e045f3f54bc53dd47ecebec335752 Mon Sep 17 00:00:00 2001 From: Michael Zhang Date: Wed, 10 Jul 2024 00:12:17 -0500 Subject: [PATCH] update --- apps/codetrack/.gitignore | 175 ++++++++++++++++++ apps/codetrack/README.md | 15 ++ apps/codetrack/bun.lockb | Bin 0 -> 34781 bytes apps/codetrack/index.ts | 18 ++ apps/codetrack/manifest.yml | 19 ++ apps/codetrack/package.json | 15 ++ apps/codetrack/tsconfig.json | 22 +++ apps/std/manifest.yml | 7 + biome.json | 32 ++-- packages/panorama-daemon/bun.lockb | Bin 99849 -> 109388 bytes packages/panorama-daemon/package.json | 5 +- packages/panorama-daemon/src/apps/index.ts | 78 ++++++++ packages/panorama-daemon/src/apps/manifest.ts | 26 +++ packages/panorama-daemon/src/db.ts | 2 +- packages/panorama-daemon/src/index.ts | 42 ++--- packages/panorama-daemon/src/models.ts | 5 +- packages/panorama-daemon/src/routes/node.ts | 33 ++++ 17 files changed, 454 insertions(+), 40 deletions(-) create mode 100644 apps/codetrack/.gitignore create mode 100644 apps/codetrack/README.md create mode 100755 apps/codetrack/bun.lockb create mode 100644 apps/codetrack/index.ts create mode 100644 apps/codetrack/manifest.yml create mode 100644 apps/codetrack/package.json create mode 100644 apps/codetrack/tsconfig.json create mode 100644 apps/std/manifest.yml create mode 100644 packages/panorama-daemon/src/apps/index.ts create mode 100644 packages/panorama-daemon/src/apps/manifest.ts create mode 100644 packages/panorama-daemon/src/routes/node.ts diff --git a/apps/codetrack/.gitignore b/apps/codetrack/.gitignore new file mode 100644 index 0000000..468f82a --- /dev/null +++ b/apps/codetrack/.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/apps/codetrack/README.md b/apps/codetrack/README.md new file mode 100644 index 0000000..445afce --- /dev/null +++ b/apps/codetrack/README.md @@ -0,0 +1,15 @@ +# codetrack + +To install dependencies: + +```bash +bun install +``` + +To run: + +```bash +bun run index.ts +``` + +This project was created using `bun init` in bun v1.0.25. [Bun](https://bun.sh) is a fast all-in-one JavaScript runtime. diff --git a/apps/codetrack/bun.lockb b/apps/codetrack/bun.lockb new file mode 100755 index 0000000000000000000000000000000000000000..85d0d2055509b41cd5bf9a056f5a41e00544fcbc GIT binary patch literal 34781 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 Bwh#aS literal 0 HcmV?d00001 diff --git a/apps/codetrack/index.ts b/apps/codetrack/index.ts new file mode 100644 index 0000000..0f5819d --- /dev/null +++ b/apps/codetrack/index.ts @@ -0,0 +1,18 @@ +import type { Context } from "koa"; + +export async function createHeartbeats(ctx: Context) { + const results = []; + for (const heartbeat of ctx.request.body) { + console.log("heartbeat", heartbeat); + const resp = await fetch("http://localhost:3000/node", { + method: "PUT", + }); + const data = await resp.json(); + results.push({ + id: data.id, + }); + } + ctx.status = 400; + console.log("results", results); + ctx.body = {}; +} diff --git a/apps/codetrack/manifest.yml b/apps/codetrack/manifest.yml new file mode 100644 index 0000000..ad68492 --- /dev/null +++ b/apps/codetrack/manifest.yml @@ -0,0 +1,19 @@ +name: panorama/codetrack + +depends: +- name: panorama + +code: dist/index.js + +attributes: +- name: heartbeat + type: interface + requires: + - panorama::time/start + +- name: project + type: option + +endpoints: +- route: /api/v1/users/current/heartbeats.bulk + handler: createHeartbeats \ No newline at end of file diff --git a/apps/codetrack/package.json b/apps/codetrack/package.json new file mode 100644 index 0000000..f82c12f --- /dev/null +++ b/apps/codetrack/package.json @@ -0,0 +1,15 @@ +{ + "name": "codetrack", + "module": "index.ts", + "type": "module", + "devDependencies": { + "@types/bun": "latest", + "@types/koa": "^2.15.0" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "@koa/bodyparser": "^5.1.1" + } +} \ No newline at end of file diff --git a/apps/codetrack/tsconfig.json b/apps/codetrack/tsconfig.json new file mode 100644 index 0000000..dcd8fc5 --- /dev/null +++ b/apps/codetrack/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "lib": ["ESNext"], + "target": "ESNext", + "module": "ESNext", + "moduleDetection": "force", + "jsx": "react-jsx", + "allowJs": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "verbatimModuleSyntax": true, + "noEmit": true, + + /* Linting */ + "skipLibCheck": true, + "strict": true, + "noFallthroughCasesInSwitch": true, + "forceConsistentCasingInFileNames": true + } +} diff --git a/apps/std/manifest.yml b/apps/std/manifest.yml new file mode 100644 index 0000000..23f34ce --- /dev/null +++ b/apps/std/manifest.yml @@ -0,0 +1,7 @@ +name: panorama + +attributes: +- name: time/start + type: datetime +- name: time/end + type: datetime diff --git a/biome.json b/biome.json index 4724444..0d449a7 100644 --- a/biome.json +++ b/biome.json @@ -1,18 +1,18 @@ { - "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json", - "organizeImports": { - "enabled": true - }, - "formatter": { - "enabled": true, - "indentWidth": 2, - "indentStyle": "space", - "lineWidth": 100 - }, - "linter": { - "enabled": true, - "rules": { - "recommended": true - } - } + "$schema": "https://biomejs.dev/schemas/1.4.1/schema.json", + "organizeImports": { + "enabled": true + }, + "formatter": { + "enabled": true, + "indentWidth": 2, + "indentStyle": "space", + "lineWidth": 80 + }, + "linter": { + "enabled": true, + "rules": { + "recommended": true + } + } } diff --git a/packages/panorama-daemon/bun.lockb b/packages/panorama-daemon/bun.lockb index c5fc541beb322f58386da4c87808bd4180769716..2ba2afaed373b1983c17f380c7ecc8c8a42549fc 100755 GIT binary patch delta 23257 zcmeHvcR*A}_x8@p3acU_bpZub5UB!E6x@8xA z#%QduMPm{*8l$l*mPAc7-*awJlGo(-wLjmRJbUJudd`_Mb1!?>nKHfVsY_L72R53& zE$pMb?{$ls{dsj~s;c@rvuW<&K8L2J_=G=s)~8nd$NS2diZ1t}cz1JsH;ITjDjSum zU}RRZNJb=Nq*G$Z5L5Ed2$d>-M0QG6ZiZ?^LhhhEX1_)t3gh%kmP36vjs1Kv?;n}r#qriskF$jswy?;0%=VZxGi(HooXrNnle*U za!vVp{do*?GTWg6bv_J|+GdzCP1y-~d2g&#Do60=kd$ADk;$k_kToIi%lTPoTnjuA zlEzYW_`9--QN2)!+RH|{yi@iEbVwbIKq)Dd$t#$Xmd2!EFK}CRkmm-tm^rgbEf{bYF>`Q*|+ zTqQjQB@7vq0zFFV%GSvqm=lywyNG;JFFPP3AzP(Vb@!9n?SQ07E`>z>Rt0%^xfB|zX!s8q1)-3n zz!gL{4LB)4Qa?W-H(($fXc;IC&5Nqz`{ zM7_joM_4<@8>mbA~hJCl@O4YmzDVqxHHO?s8ga%S@xx2QrwvG5^{4BMyQIx z$-a*uX~5%rVO4kA!;z9%8K#VsfaF9~IX_<2-P{Jbq-;t`z(7-OUVfC!8_O~&OSDPl z3XWw|Fgr?8{uU&)t0mW)7%h#N4oS8or(}y;q(&|u-&`7fU~WoYD!P0fA+}c+RB)4J zkoguUseB%iR6Yn<8*)T??hsNSAt@;(J3mj=4QU$oc?)TnjFkM;tmHhEqDEeRLjI7v zl)R5nk9-{3QtCG;D$i<-CU43B0nV$jdJJ%*R@ts!}jQj z?1_f7hD=S!LzJLg=Jz2f=0auZtH?HzA5l?!+vB;~uwvYIUaKs{3Mku0kkC0+>Wf&7}Wk|I?g$#WM`kK`?I z@^}g5n~~^-23p8WNScC=A!)#u9i$Gg%LCMhg5+9knBp>Vjg#69kR{5*{IvlGYP&tZ z=N1rt^M2cp%!iu1uCYIU-q!waW52$>ZE60G!n4d4Ep|=WH|zdSW!q0##afz=Vb-(NV~Ct&gM&H6pb-J?C$9Si%i z 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 nM6GveVRJX;1X);MOl>+Kj;-Rge<0<8zG delta 17585 zcmeI3d3Y36*8lq!X`llU!j^;tNI({W1d;$966hxEP=JI15=4;zfka3`5&|SFNl*|4 z1iWIgh{~oYh#-Q1f}-PsxC}bD;WFs!GKve18>skxPF1z=&dba*?;pQEe$Qj^eEZz* zx#uqD+;eYrs;QUWsC~=w+N+YT`lwFg;KE-T_x`rS#)tC@2JBAx^ov?27j_%lA@S$u zE5ova6^oTeuG*`!TTL&jw70D=sbcROFUUE2DaX;qla` zz9KoTNUTS>ylX9wCklOAWIbdoG8`Ed=EzL!BH<~k&ql>$dp#Z>WIa-hKSD-GrA|<- zBID{f+Jd6|ywZYMWmoGg6;-vZuA}wQP}&w26cxJ4NeB>5 z8RVa%HhD%tc>z(2)+I)(+!0^--f$ zWT&Q%qsi_VE%jNWm5SDpwXROcjdA8S7AbT4=b+5hHMQE0dob2ntMy3nBtOoX`*EbW zeAt!OBPFD_VJGvLmRmk8k9nlDbmkX|Kzq21)}g#w0q^pOsshIF$gdgUbMCT>S@$`ay5YsJ;JUSjT~g5T(0rt>~h>|aHS zKYvDIH>r9e^Og0OR8S<*FFT&#*|TAEjukAAtM zH>z*c6?M8)&4J34ryym@qmbWUeq<=8Jf+MN znnu~I+*##id1WcsN&6Qwong!SdOXeHlaZp&a%IqwRn^H9WZrFDnZZcH_aozx{RTJ_ zOGC;8!m$(Ce4sP&ldgU#_L75JkmAT{qzssdl>VBA#$kez1ROi$WNT0L$iYs-J6uVn zovimK9%&T!-RE(8SNSH^*}FZmp;FiDo{fXXrygC|IAHvy^r6N9b%Q>KZik`Mn*@wg zhOTT9P=oZrCPCju?kfRCs;iGS@%v7}SgPvUIL)9K9uzP4UYF}oLOHxZT&t0R3H+r0WbCf}gMJDOvndg+R&pl@+)kB9A0?Gv4` zueLrP6)--nt<$3es=cm=4yrtTFgj>#tfS9I2aKQU==7L?uUB1jpib|^s=|dI4YKDD%o!)|}MmW)B z%ox9~(q*-EO|;*40LE%o*RokNmS5H(OdoBVrYl+oeUGD&m=ds9zwgW6nXFSQ*58>u z%v%cUs}DEH@I6XN{I4Zz0 z!n(nTWlO)W8;4I1n37n^)Y}GqThYk?wPdY~PnzoUZ3C*K?%6J=R_Ka$LEq;btVvE6 z_|_)MnH}Md^Q&ClvwhIF4xM!C4EZWdCV&MAR43YDUVA!2U@{$2jlLGR%&<4Wk!XFW zL%{c~t7JZ`XFImGq<$S;!wQzb2J7C9GkgarIYE+@{SlU~dpFDQC38LvvIkHJe%~UP ztog^jg4kkpNe3nd>r&o>7q=~PP} z?cg`wj??L}0bh$2PV}AS%7w{xaQ4y$m?XRHpzjn+l7ui3{h&UW7*sX-Tw>7oWXs=! z%2xfgrLIg0_(rsH=H)bA0h2&DaXjqSF(`|C7A8jy=L2&-6Yueiw1<(pUVP1wI%hMj zf=NKgOm^!_3A(atz}KX;GfT3G{22`s6MO%A*9+5!6El2oP?8BKd*N&AgULbP$Tm*r z_Q~g64`X#=GkhOP30rz3Y#CR2JiVQkB*a{pWGhpLJ@GrMY3KJ{(Kb{^wyIJ3U~15} z2A#}@@4KYTmoO)yB$rCm72Sfq`RzivKvw$>!TMrvAE3rB?R0wgfUisYP)^D=msWRa8{xSCTZy$MvuA7 zqifj7-@DAo{nU=3oxm!2%V9w~%#TuXlZ4g#9_D16tn8mUh0X$I>RSVI$70!DhspAA z4s(dr(&u^xeSJB>#52P_I_JY=QJh1+ZK4xM!rI2~%Y(@tuy0zvEij1>UeNPTuBDyQ zDor2E;EduxkmHdB=Fqq=NvHP;c;7+URnAuxt`GJKdPj7jQ@tQ6!?%c%bZF;;_kCDj zJH+wKcm#So^uGBp$qeTtdaA2V4+MPS+``1fUQTZgEKT0+$%rUKqb5 z>&jrj+nxB?dFWk0X@t&wJyq{+RM&SLCcz{eBtax+xD!5MSHom%=Z>XeX)w;oZrtr* z5;$j1w;@ha=PdC;SPsmdv+wUPX9N|bPi`vGr;{2QCga&RKHmx0NT(P12Epkm2dFbb zHbu#CMrURgOaef=)@gMi(uuPcZS=W*LElFtsjR%6nX0j_=pW?E&B6Xb4O8_D@lYt_Ucce6GFKMH$)dzuP8_AjUNGs=K0wtBeQs#bxA~$) zoZtHvteZaEF~e6s7>YTkk{YJZnL+P*bi-s+95Zt-srE>#|DhC19T-~ z;{bgK@wWl`Jfht|ot_==O=Czo*T|Q6zj60KeJDF%yf#pur>^-RXGYYG^&1lh>B^jd zarYp72ytqVKA#it#j+JhUTGfVH^vRtmE#y_us(!%Z?HZ;E*>7}j)go9;3rnnl&NVmIEBjpk)AzCAZ>)%Off2W6F zONV!fHP@w53^oGk@E#zSNa34=aETPV%^(ci2jmhdcH4o3W(SZb^!A}$>&QlwzXQ_l2Ujmr z?0$6RPpW>uy3#fHcT!B+x_1AG6w`LDy-1m82Um7f z_%EAV9^Q&gihTSZWfb;9+;$>m023*yVXppP%20^JKx~J*4MZAx!Ne%Nb7Ecl7LeuE zh?K<`>+;J_R|9_Fv?i$)j)A4S5q>jz?*>kuSo!Crp>qH8lh?F_yx%x|`=x=cK zBBeN;582;ku3n@RXSw_`GTcs(xfq1$_ik|Z&myW+a zbLDcR*xlyxmB`RlkAgrhWNS}6g-+m)oA)0#?>}zdf84xJa~8-YQqGMJg>Z@d|HsYyKf9UBo$LRPn|Gc}_5bBJ}q#o^KUr>(5}@icRIyXJKoLBlMV=rmCkm&CJ$=XGUmm ziK!xVR!O%03APW$L(tM}y}cwt=ariL^s@(+QyQV0mYF(w=FkSj4mn9*3sn=`Xnq?C(O>)FTfVhHdPCK0ycMcgzi4aRIPN) zoNV1;PK5p#mY|d8X6v_LYv!7&jXn)qIv4*cOx0GeuE4(v{DZaEnU(kl+g53+tMpme z+DiPJXR1zm(>(l}hkx@;o*ie+$3NIUSQo7p;NN`wTVSeWy$6=F0RI-6Dplt$#J`33 z2kWjQtMCt2US%r3J_^gP!oO-$_0%QR_*ad8une70gMYBaHKyvVPr&BZ;NK#Xr~fsJ z@NW_R!7_F7jra#!bEB#H>C>>KH{#z-rW&AE--LfR;U8>}&RmRtux*P?HAJ6)Ee|MUyLYLf$e|O>^Y@SZA@DH}wGSvco0yfvezqKa62-d8{zqR-W ztI^5p@DH|TovCirr(sLi;oo{wE!L~opifXyYcTHQ)!)h5B}YQf3P(=@?QLd zmEUVBOCN>h--~~nOtnszY{I`y_y^md6E@=?Z1HAOZPX`Vb2sDP7E|4$YqsFu7W{*4 z(#c!#54L8jskZ3Tu%%n^Z=0#M>DAltZyWx>?$??3;U8?2-+_NSO?6P0?8LvF_y>DjCp?6Iu*DCV>aacmoBI&{?K0Jqx@H&t?ZQ9U zQJwrS{=wEfY^rDUY1q<-@o%@Op4F>&{YEE#lOAy_o%5(=smEUNAYi;sb1H)`|xic{=rV_$o=>S zE8lObH}z3i{(k&>%v5jdlE?7xG5mv_(g_Ff54QM#sov8kU~>=P-$7G-plc4|-$DF? zoz}^R@DH};kf}b>r(sJE;osw?`dF`i9RD82KiH=_^9lTeZF|C0f7fSWYoEZs!>0N| zZ#s;Bhw<--X?*F?>CcVTKS4J>XBuC7^ykp+M8dLjMZ=her>6 zVXUtERD^!$1=IN6qt8PRK(9My8b5mUgU80|{G$;%;ziRq=h4@^I95kL9ib0HfA;9` z<74#;(8A-U@vBE4g3f&=LbrIyG=B5w8(tc#JN!99zx6w2C+gtzD8OoSjTTghsxvg9Kmzc)k$CW z^D*StuC6w+8gNy4zTs0E$RmITK(4bubnSo zAXn(YU?k-{TVp>YwC&_?m#ILU`~^rv#jydQ(8yCmQ8ocx+*wOYDK-UHxH{$PNCD3} z(F4QP$uqS5KrUHjX%Y<{69rco3W-__I3fzJsyeP=bI6mn$_bk^js-_uozJz4gFoZy zLXS9Gz&E=(dE6;&TY{~wuD+{l1z!UsY7vgE$`cO>{lFn!O2`v{JTH|?o|j5wT7$-v zB^u3;Qr!l81jNfIS9c|4$rZWenX1^e1#gRjE5_BeqkO-lrp#0xx{6_YAYql6#=5!= zl!t+MAWs^_?kX@G#Df;Dt|R3UuCAr4lV@8aU0o|z*BPGW>f(E%6z}*M-1Z;=+1fQs zqAZgUFWb1frLtaJ(e)%DWt3Kc+kl)2t3VC75y&=Q43+@d-b=x) zpc2djbHH3M2}}l4fNc3bAQSWj{lNerDIiNOD=aI@Y~_bztGKoDRxM&)%j&f$AE16gWwQ&0vrZMz?0x9aFiys z+Nj8?eUQyy3)l*_feD}&r~sv)49o)Kz%@X2tO*7KKS%?qpc|0q%N3v!%mbUyN%G5% zjszdWzW`r?uO)rtxR)*a0r(q`A9&sXZ-dvs>)?5?6YK&HgGWGbP!7g}YrzoE9o)nq zRp4&Q=fOGf12_d<1joVkAO}>-uNOa4_yzn3-UTm#mqCzD@`2oMdAQ@XZ$L^y1EBJ`;YAUPpRA^Ff8!~kj66f^;iK`mgNZL5NJwqq+? z0gC|jK|N3#ctIHOfx4g$2nSLpx(1-(B|MUHBM=4Toaqc&gI1sgXbEE7avU-qB!DX= z3)@iW0HpJFKn7?FWROlk>O|k(l^u~+fh3R!B>j^?7r+L$W8MQPA?Xi-AOL!So

% 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 SU8eQ7t*Z88nOju7y8jKCVFh^r diff --git a/packages/panorama-daemon/package.json b/packages/panorama-daemon/package.json index e34343b..62ada9a 100644 --- a/packages/panorama-daemon/package.json +++ b/packages/panorama-daemon/package.json @@ -11,12 +11,15 @@ "typescript": "^5.0.0" }, "dependencies": { + "@koa/bodyparser": "^5.1.1", "@koa/router": "^12.0.1", "koa": "^2.15.3", "koa-json": "^2.0.2", "reflect-metadata": "^0.2.2", "sqlite3": "^5.1.7", "typeorm": "^0.3.20", - "uuidv7": "^1.0.1" + "uuidv7": "^1.0.1", + "yaml": "^2.4.5", + "zod": "^3.23.8" } } \ No newline at end of file diff --git a/packages/panorama-daemon/src/apps/index.ts b/packages/panorama-daemon/src/apps/index.ts new file mode 100644 index 0000000..ea762e2 --- /dev/null +++ b/packages/panorama-daemon/src/apps/index.ts @@ -0,0 +1,78 @@ +import { join } from "node:path"; +import { parse } from "yaml"; +import { readFile } from "node:fs/promises"; +import Router from "@koa/router"; +import { manifestSchema, type Manifest } from "./manifest"; +import { dataSource } from "../db"; +import { App, Attribute } from "../models"; + +export interface CustomApp extends Manifest { + sanitizedName: string; + router: Router; +} + +export function sanitizeName(name: string): string { + return name.replaceAll("/", "__"); +} + +export async function loadApps(): Promise> { + const apps = new Map(); + const paths = ["/Users/michael/Projects/panorama/apps/codetrack"]; + + for (const path of paths) { + const app = await loadApp(path); + apps.set(app.name, app); + } + return apps; +} + +export async function loadApp(path: string): Promise { + const manifestPath = join(path, "manifest.yml"); + const manifestRaw = parse(await readFile(manifestPath, "utf-8")); + const manifest = manifestSchema.parse(manifestRaw); + const sanitizedName = sanitizeName(manifest.name); + + // load code + const codePath = join(path, manifest.code); + const codeModule = await import(codePath); + + // wire up routes + const router = new Router(); + for (const endpoint of manifest.endpoints || []) { + const func = codeModule[endpoint.handler]; + router.all(endpoint.route, func); + } + + await dataSource.transaction(async (em) => { + const app = await em + .createQueryBuilder() + .select("app") + .from(App, "app") + .where("app.id = :id", { id: sanitizedName }) + .getOne(); + let appId = app?.id; + + console.log("app id", appId); + + if (!appId) { + const result = await em.getRepository(App).insert({ + id: sanitizedName, + name: manifest.name, + }); + + appId = result.identifiers[0].id; + } + + // register all the attributes + for (const attribute of manifest.attributes || []) { + await em + .getRepository(Attribute) + .upsert({ appId, name: attribute.name, type: attribute.type }, [ + "appId", + "name", + ]); + } + }); + + return { ...manifest, sanitizedName, router }; +} diff --git a/packages/panorama-daemon/src/apps/manifest.ts b/packages/panorama-daemon/src/apps/manifest.ts new file mode 100644 index 0000000..3befae8 --- /dev/null +++ b/packages/panorama-daemon/src/apps/manifest.ts @@ -0,0 +1,26 @@ +import { z } from "zod"; + +export const manifestSchema = z.object({ + name: z.string(), + code: z.string(), + + attributes: z + .array( + z.object({ + name: z.string(), + type: z.string(), + }), + ) + .optional(), + + endpoints: z + .array( + z.object({ + route: z.string(), + handler: z.string(), + }), + ) + .optional(), +}); + +export type Manifest = z.infer; diff --git a/packages/panorama-daemon/src/db.ts b/packages/panorama-daemon/src/db.ts index cce3e65..714b257 100644 --- a/packages/panorama-daemon/src/db.ts +++ b/packages/panorama-daemon/src/db.ts @@ -7,7 +7,7 @@ const AppDataSource = new DataSource({ entities: [PNode, App, Attribute, NodeHasAttribute], synchronize: true, - logging: true, + // logging: true, migrationsTableName: "migrations", migrations: ["migrations/*"], diff --git a/packages/panorama-daemon/src/index.ts b/packages/panorama-daemon/src/index.ts index 274f8b8..a69cbd1 100644 --- a/packages/panorama-daemon/src/index.ts +++ b/packages/panorama-daemon/src/index.ts @@ -3,12 +3,27 @@ import json from "koa-json"; import Router from "@koa/router"; import { dataSource } from "./db"; import { PNode } from "./models"; -import { uuidv7 } from "uuidv7"; +import { nodeRouter } from "./routes/node"; +import { loadApps, sanitizeName } from "./apps"; +import { bodyParser } from "@koa/bodyparser"; const app = new Koa(); const router = new Router(); app.use(json()); +app.use(bodyParser()); + +app.use(async (ctx, next) => { + console.log("Got a request from %s for %s", ctx.request.ip, ctx.method, ctx.path); + return next(); +}); + +const apps = await loadApps(); +for (const [name, customApp] of apps.entries()) { + console.log("name", name); + const sanitizedName = sanitizeName(name); + router.use(`/apps/${sanitizedName}`, customApp.router.routes()); +} router.get("/", async (ctx: Context) => { const nodeRepo = dataSource.getRepository(PNode); @@ -16,27 +31,12 @@ router.get("/", async (ctx: Context) => { ctx.body = { nodes: numNodes }; }); -router.put("/node", async (ctx) => { - const id = uuidv7(); - await dataSource.getRepository(PNode).insert({ id }); - ctx.body = { id }; -}); +router.use("/node", nodeRouter.routes()); -router.get("/node/:id", async (ctx) => { - const query = dataSource - // .getRepository(Node) - .createQueryBuilder() - .select("node") - .from(PNode, "node") - .where("node.id = :id", { id: ctx.params.id }); - const node = await query.getOne(); - if (node === null) { - ctx.status = 404; - ctx.body = { error: "Not found" }; - return; - } - ctx.body = { node }; -}); +console.log( + "routes", + router.stack.map((i) => i.path), +); app.use(router.routes()).use(router.allowedMethods()); diff --git a/packages/panorama-daemon/src/models.ts b/packages/panorama-daemon/src/models.ts index 9eff94e..d14eb59 100644 --- a/packages/panorama-daemon/src/models.ts +++ b/packages/panorama-daemon/src/models.ts @@ -1,6 +1,6 @@ import { Column, Entity, ManyToOne, OneToMany, PrimaryColumn } from "typeorm"; -@Entity() +@Entity({ name: "node" }) export class PNode { @PrimaryColumn() id!: string; @@ -11,6 +11,8 @@ export class App { @PrimaryColumn() id!: string; + name!: string; + @OneToMany( () => Attribute, (attr) => attr.appId, @@ -30,6 +32,7 @@ export class Attribute { @PrimaryColumn() name!: string; + @Column() type!: string; } diff --git a/packages/panorama-daemon/src/routes/node.ts b/packages/panorama-daemon/src/routes/node.ts new file mode 100644 index 0000000..429261e --- /dev/null +++ b/packages/panorama-daemon/src/routes/node.ts @@ -0,0 +1,33 @@ +import Router from "@koa/router"; +import { PNode } from "../models"; +import { uuidv7 } from "uuidv7"; +import { dataSource } from "../db"; + +export const nodeRouter = new Router(); + +nodeRouter.put("/", async (ctx) => { + const id = uuidv7(); + const body = ctx.request.body; + await dataSource.getRepository(PNode).insert({ id }); + ctx.body = { id }; +}); + +nodeRouter.post("/query", async (ctx) => {}); + +nodeRouter.get("/:id", async (ctx) => { + const query = dataSource + .createQueryBuilder() + .select("node") + .from(PNode, "node") + .where("node.id = :id", { id: ctx.params.id }); + + const node = await query.getOne(); + if (node === null) { + ctx.status = 404; + ctx.body = { error: "Not found" }; + return; + } + ctx.body = { node }; +}); + +nodeRouter.post("/:id", async (ctx) => {});