@mxpicture/print-backlog 0.0.0-stage → 0.0.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (63) hide show
  1. package/db/migrations/001_baseline.sql +94 -0
  2. package/dist/public/assets/index-CLP1jBWd.js +1 -0
  3. package/dist/public/assets/index-DxpzozYw.css +1 -0
  4. package/dist/public/favicon.svg +1 -0
  5. package/dist/public/index.html +15 -0
  6. package/dist/server/api/health.d.ts +12 -0
  7. package/dist/server/api/health.d.ts.map +1 -0
  8. package/dist/server/api/health.js +30 -0
  9. package/dist/server/api/health.js.map +1 -0
  10. package/dist/server/api/hooks.d.ts +9 -0
  11. package/dist/server/api/hooks.d.ts.map +1 -0
  12. package/dist/server/api/hooks.js +29 -0
  13. package/dist/server/api/hooks.js.map +1 -0
  14. package/dist/server/api/queues.d.ts +5 -0
  15. package/dist/server/api/queues.d.ts.map +1 -0
  16. package/dist/server/api/queues.js +30 -0
  17. package/dist/server/api/queues.js.map +1 -0
  18. package/dist/server/app.d.ts +10 -0
  19. package/dist/server/app.d.ts.map +1 -0
  20. package/dist/server/app.js +11 -0
  21. package/dist/server/app.js.map +1 -0
  22. package/dist/server/auth.d.ts +11 -0
  23. package/dist/server/auth.d.ts.map +1 -0
  24. package/dist/server/auth.js +31 -0
  25. package/dist/server/auth.js.map +1 -0
  26. package/dist/server/bambuddy.d.ts +15 -0
  27. package/dist/server/bambuddy.d.ts.map +1 -0
  28. package/dist/server/bambuddy.js +31 -0
  29. package/dist/server/bambuddy.js.map +1 -0
  30. package/dist/server/cli.d.ts +3 -0
  31. package/dist/server/cli.d.ts.map +1 -0
  32. package/dist/server/cli.js +123 -0
  33. package/dist/server/cli.js.map +1 -0
  34. package/dist/server/config.d.ts +19 -0
  35. package/dist/server/config.d.ts.map +1 -0
  36. package/dist/server/config.js +63 -0
  37. package/dist/server/config.js.map +1 -0
  38. package/dist/server/db.d.ts +8 -0
  39. package/dist/server/db.d.ts.map +1 -0
  40. package/dist/server/db.js +18 -0
  41. package/dist/server/db.js.map +1 -0
  42. package/dist/server/http.d.ts +90 -0
  43. package/dist/server/http.d.ts.map +1 -0
  44. package/dist/server/http.js +266 -0
  45. package/dist/server/http.js.map +1 -0
  46. package/dist/server/log.d.ts +4 -0
  47. package/dist/server/log.d.ts.map +1 -0
  48. package/dist/server/log.js +4 -0
  49. package/dist/server/log.js.map +1 -0
  50. package/dist/server/schema.d.ts +14 -0
  51. package/dist/server/schema.d.ts.map +1 -0
  52. package/dist/server/schema.js +67 -0
  53. package/dist/server/schema.js.map +1 -0
  54. package/dist/server/version.d.ts +3 -0
  55. package/dist/server/version.d.ts.map +1 -0
  56. package/dist/server/version.js +4 -0
  57. package/dist/server/version.js.map +1 -0
  58. package/dist/shared/types.d.ts +35 -0
  59. package/dist/shared/types.d.ts.map +1 -0
  60. package/dist/shared/types.js +11 -0
  61. package/dist/shared/types.js.map +1 -0
  62. package/package.json +44 -4
  63. package/README.md +0 -3
@@ -0,0 +1,94 @@
1
+ -- Print Backlog: queues of unsliced models, one status per item, immutable
2
+ -- versions, and the print events Bambuddy's webhook sends. Everything lives in
3
+ -- schema `backlog`, which `print-backlog setup` creates before dbmate runs
4
+ -- (dbmate's own table is backlog.schema_migrations). Strict DDL, forward only:
5
+ -- never edit a migration that was released.
6
+ -- migrate:up
7
+ CREATE TYPE backlog.item_status AS ENUM('queued', 'skipped', 'done', 'error');
8
+
9
+ CREATE TABLE backlog.queue (
10
+ id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
11
+ name TEXT NOT NULL UNIQUE CHECK (btrim(name) <> ''),
12
+ position BIGINT NOT NULL,
13
+ -- Bambuddy's print events may set the status of this queue's items.
14
+ auto_status BOOLEAN NOT NULL DEFAULT TRUE,
15
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now()
16
+ );
17
+
18
+ CREATE TABLE backlog.item (
19
+ id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
20
+ queue_id BIGINT NOT NULL REFERENCES backlog.queue ON DELETE RESTRICT,
21
+ position BIGINT NOT NULL,
22
+ title TEXT NOT NULL CHECK (btrim(title) <> ''),
23
+ status backlog.item_status NOT NULL DEFAULT 'queued',
24
+ status_changed_at TIMESTAMPTZ NOT NULL DEFAULT now(),
25
+ -- A print may have been this item, but the match was not certain.
26
+ needs_confirm BOOLEAN NOT NULL DEFAULT FALSE,
27
+ notes TEXT,
28
+ source_url TEXT,
29
+ current_version_id BIGINT,
30
+ bambuddy_file_id INTEGER,
31
+ bambuddy_sent_at TIMESTAMPTZ,
32
+ deleted_at TIMESTAMPTZ,
33
+ created_at TIMESTAMPTZ NOT NULL DEFAULT now(),
34
+ created_by TEXT NOT NULL
35
+ );
36
+
37
+ CREATE INDEX item_queue_position ON backlog.item (queue_id, position)
38
+ WHERE
39
+ deleted_at IS NULL;
40
+
41
+ CREATE TABLE backlog.version (
42
+ id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
43
+ item_id BIGINT NOT NULL REFERENCES backlog.item ON DELETE CASCADE,
44
+ n INTEGER NOT NULL CHECK (n > 0),
45
+ filename TEXT NOT NULL,
46
+ ext TEXT NOT NULL,
47
+ size BIGINT NOT NULL CHECK (size >= 0),
48
+ sha256 CHAR(64) NOT NULL,
49
+ stored_path TEXT NOT NULL,
50
+ has_thumbnail BOOLEAN NOT NULL DEFAULT FALSE,
51
+ comment TEXT,
52
+ uploaded_at TIMESTAMPTZ NOT NULL DEFAULT now(),
53
+ uploaded_by TEXT NOT NULL,
54
+ UNIQUE (item_id, n),
55
+ UNIQUE (item_id, sha256)
56
+ );
57
+
58
+ ALTER TABLE backlog.item
59
+ ADD CONSTRAINT item_current_version_fk FOREIGN KEY (current_version_id) REFERENCES backlog.version
60
+ DEFERRABLE INITIALLY DEFERRED;
61
+
62
+ CREATE TABLE backlog.status_event (
63
+ id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
64
+ item_id BIGINT NOT NULL REFERENCES backlog.item ON DELETE CASCADE,
65
+ from_status backlog.item_status,
66
+ to_status backlog.item_status NOT NULL,
67
+ at TIMESTAMPTZ NOT NULL DEFAULT now(),
68
+ -- the SSO user name, or 'bambuddy' for a webhook
69
+ by TEXT NOT NULL,
70
+ source TEXT NOT NULL CHECK (source IN ('ui', 'webhook')),
71
+ -- printer, duration, filament_g, reason, the raw filename
72
+ detail JSONB
73
+ );
74
+
75
+ CREATE INDEX status_event_item ON backlog.status_event (item_id, at);
76
+
77
+ CREATE TABLE backlog.print_event (
78
+ id BIGINT GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
79
+ received_at TIMESTAMPTZ NOT NULL DEFAULT now(),
80
+ event TEXT NOT NULL,
81
+ filename TEXT,
82
+ printer TEXT,
83
+ status TEXT,
84
+ payload JSONB NOT NULL,
85
+ matched_item_id BIGINT REFERENCES backlog.item ON DELETE SET NULL,
86
+ -- NULL = open (shown under "Unmatched prints")
87
+ resolution TEXT CHECK (resolution IN ('auto', 'manual', 'ignored'))
88
+ );
89
+
90
+ CREATE INDEX print_event_open ON backlog.print_event (received_at)
91
+ WHERE
92
+ resolution IS NULL;
93
+
94
+ -- migrate:down
@@ -0,0 +1 @@
1
+ (function(){let e=document.createElement(`link`).relList;if(e&&e.supports&&e.supports(`modulepreload`))return;for(let e of document.querySelectorAll(`link[rel="modulepreload"]`))n(e);new MutationObserver(e=>{for(let t of e)if(t.type===`childList`)for(let e of t.addedNodes)e.tagName===`LINK`&&e.rel===`modulepreload`&&n(e)}).observe(document,{childList:!0,subtree:!0});function t(e){let t={};return e.integrity&&(t.integrity=e.integrity),e.referrerPolicy&&(t.referrerPolicy=e.referrerPolicy),t.credentials=e.crossOrigin===`use-credentials`?`include`:e.crossOrigin===`anonymous`?`omit`:`same-origin`,t}function n(e){if(e.ep)return;e.ep=!0;let n=t(e);fetch(e.href,n)}})();var e,t,n,r,i,a,o,s,c,l,u,d,f,p,m={},h=[],g=/acit|ex(?:s|g|n|p|$)|rph|grid|ows|mnc|ntw|ine[ch]|zoo|^ord|itera/i,_=Array.isArray;function v(e,t){for(var n in t)e[n]=t[n];return e}function y(e){e&&e.parentNode&&e.parentNode.removeChild(e)}function b(t,n,r){var i,a,o,s={};for(o in n)o==`key`?i=n[o]:o==`ref`?a=n[o]:s[o]=n[o];if(arguments.length>2&&(s.children=arguments.length>3?e.call(arguments,2):r),typeof t==`function`&&t.defaultProps!=null)for(o in t.defaultProps)s[o]===void 0&&(s[o]=t.defaultProps[o]);return x(t,s,i,a,null)}function x(e,r,i,a,o){var s={type:e,props:r,key:i,ref:a,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:o??++n,__i:-1,__u:0};return o==null&&t.vnode!=null&&t.vnode(s),s}function S(e){return e.children}function C(e,t){this.props=e,this.context=t}function w(e,t){if(t==null)return e.__?w(e.__,e.__i+1):null;for(var n;t<e.__k.length;t++)if((n=e.__k[t])!=null&&n.__e!=null)return n.__e;return typeof e.type==`function`?w(e):null}function T(e){if(e.__P&&e.__d){var n=e.__v,r=n.__e,i=[],a=[],o=v({},n);o.__v=n.__v+1,t.vnode&&t.vnode(o),I(e.__P,o,n,e.__n,e.__P.namespaceURI,32&n.__u?[r]:null,i,r??w(n),!!(32&n.__u),a),o.__v=n.__v,o.__.__k[o.__i]=o,te(i,o,a),n.__e=n.__=null,o.__e!=r&&E(o)}}function E(e){if((e=e.__)!=null&&e.__c!=null)return e.__e=e.__c.base=null,e.__k.some(function(t){if(t!=null&&t.__e!=null)return e.__e=e.__c.base=t.__e}),E(e)}function D(e){(!e.__d&&(e.__d=!0)&&r.push(e)&&!O.__r++||i!=t.debounceRendering)&&((i=t.debounceRendering)||a)(O)}function O(){try{for(var e,t=1;r.length;)r.length>t&&r.sort(o),e=r.shift(),t=r.length,T(e)}finally{r.length=O.__r=0}}function k(e,t,n,r,i,a,o,s,c,l,u){var d,f,p,g,_,v,y=r&&r.__k||h,b=t.length;for(c=A(n,t,y,c,b),d=0;d<b;d++)(p=n.__k[d])!=null&&(f=p.__i!=-1&&y[p.__i]||m,p.__i=d,v=I(e,p,f,i,a,o,s,c,l,u),g=p.__e,p.ref&&f.ref!=p.ref&&(f.ref&&R(f.ref,null,p),u.push(p.ref,p.__c||g,p)),_==null&&g!=null&&(_=g),4&p.__u?(c=j(p,c,e),f.__e&&(f.__e=null)):typeof p.type==`function`&&v!==void 0?c=v:g&&(c=g.nextSibling),p.__u&=-7);return n.__e=_,c}function A(e,t,n,r,i){var a,o,s,c,l,u=n.length,d=u,f=0;for(e.__k=Array(i),a=0;a<i;a++)(o=t[a])!=null&&typeof o!=`boolean`&&typeof o!=`function`?(typeof o==`string`||typeof o==`number`||typeof o==`bigint`||o.constructor==String?o=e.__k[a]=x(null,o,null,null,null):_(o)?o=e.__k[a]=x(S,{children:o},null,null,null):o.constructor===void 0&&o.__b>0?o=e.__k[a]=x(o.type,o.props,o.key,o.ref?o.ref:null,o.__v):e.__k[a]=o,c=a+f,o.__=e,o.__b=e.__b+1,s=null,(l=o.__i=M(o,n,c,d))!=-1&&(d--,(s=n[l])&&(s.__u|=2)),s==null||s.__v==null?(l==-1&&(i>u?f--:i<u&&f++),typeof o.type!=`function`&&(o.__u|=4)):l!=c&&(l==c-1?f--:l==c+1?f++:(l>c?f--:f++,o.__u|=4))):e.__k[a]=null;if(d)for(a=0;a<u;a++)(s=n[a])!=null&&!(2&s.__u)&&(s.__e==r&&(r=w(s)),re(s,s));return r}function j(e,t,n){var r,i;if(typeof e.type==`function`){for(r=e.__k,i=0;r&&i<r.length;i++)r[i]&&(r[i].__=e,t=j(r[i],t,n));return t}e.__e!=t&&(t&&e.type&&!t.parentNode&&(t=w(e)),t=n.insertBefore(e.__e,t||null));do t&&=t.nextSibling;while(t!=null&&t.nodeType==8);return t}function M(e,t,n,r){var i,a,o,s=e.key,c=e.type,l=t[n],u=l!=null&&!(2&l.__u);if(l===null&&s==null||u&&s==l.key&&c==l.type)return n;if(r>+!!u){for(i=n-1,a=n+1;i>=0||a<t.length;)if((l=t[o=i>=0?i--:a++])!=null&&!(2&l.__u)&&s==l.key&&c==l.type)return o}return-1}function N(e,t,n){t[0]==`-`?e.setProperty(t,n??``):e[t]=n==null?``:typeof n!=`number`||g.test(t)?n:n+`px`}function P(e,t,n,r,i){var a,o;n:if(t==`style`){if(typeof n==`string`)e.style.cssText=n;else{if(typeof r==`string`&&(e.style.cssText=r=``),r)for(t in r)n&&t in n||N(e.style,t,``);if(n)for(t in n)r&&n[t]==r[t]||N(e.style,t,n[t])}}else if(t[0]==`o`&&t[1]==`n`)a=t!=(t=t.replace(u,`$1`)),o=t.toLowerCase(),t=o in e||t==`onFocusOut`||t==`onFocusIn`?o.slice(2):t.slice(2),e.l||={},e.l[t+a]=n,n?r?n[l]=r[l]:(n[l]=d,e.addEventListener(t,a?p:f,a)):e.removeEventListener(t,a?p:f,a);else{if(i==`http://www.w3.org/2000/svg`)t=t.replace(/xlink(H|:h)/,`h`).replace(/sName$/,`s`);else if(t!=`width`&&t!=`height`&&t!=`href`&&t!=`list`&&t!=`form`&&t!=`tabIndex`&&t!=`download`&&t!=`rowSpan`&&t!=`colSpan`&&t!=`role`&&t!=`popover`&&t in e)try{e[t]=n??``;break n}catch{}typeof n==`function`||(n==null||!1===n&&t[4]!=`-`?e.removeAttribute(t):e.setAttribute(t,t==`popover`&&n==1?``:n))}}function F(e){return function(n){if(this.l){var r=this.l[n.type+e];if(n[c]==null)n[c]=d++;else if(n[c]<r[l])return;return r(t.event?t.event(n):n)}}}function I(e,n,r,i,a,o,s,c,l,u){var d,f,p,m,g,b,x,T,E,D,O,A,j,M,N,P,F=n.type;if(n.constructor!==void 0)return null;128&r.__u&&(l=!!(32&r.__u),o=[c=n.__e=r.__e]),(d=t.__b)&&d(n);n:if(typeof F==`function`){f=s.length;try{if(E=n.props,D=F.prototype&&F.prototype.render,O=(d=F.contextType)&&i[d.__c],A=d?O?O.props.value:d.__:i,r.__c?T=(p=n.__c=r.__c).__=p.__E:(D?n.__c=p=new F(E,A):(n.__c=p=new C(E,A),p.constructor=F,p.render=ie),O&&O.sub(p),p.state||(p.state={}),p.__n=i,m=p.__d=!0,p.__h=[],p._sb=[]),D&&p.__s==null&&(p.__s=p.state),D&&F.getDerivedStateFromProps!=null&&(p.__s==p.state&&(p.__s=v({},p.__s)),v(p.__s,F.getDerivedStateFromProps(E,p.__s))),g=p.props,b=p.state,p.__v=n,m)D&&F.getDerivedStateFromProps==null&&p.componentWillMount!=null&&p.componentWillMount(),D&&p.componentDidMount!=null&&p.__h.push(p.componentDidMount);else{if(D&&F.getDerivedStateFromProps==null&&E!==g&&p.componentWillReceiveProps!=null&&p.componentWillReceiveProps(E,A),n.__v==r.__v||!p.__e&&p.shouldComponentUpdate!=null&&!1===p.shouldComponentUpdate(E,p.__s,A)){n.__v!=r.__v&&(p.props=E,p.state=p.__s,p.__d=!1),n.__e=r.__e,n.__k=r.__k,n.__k.some(function(e){e&&(e.__=n)}),h.push.apply(p.__h,p._sb),p._sb=[],p.__h.length&&s.push(p),c=w(r);break n}p.componentWillUpdate!=null&&p.componentWillUpdate(E,p.__s,A),D&&p.componentDidUpdate!=null&&p.__h.push(function(){p.componentDidUpdate(g,b,x)})}if(p.context=A,p.props=E,p.__P=e,p.__e=!1,j=t.__r,M=0,D)p.state=p.__s,p.__d=!1,j&&j(n),d=p.render(p.props,p.state,p.context),h.push.apply(p.__h,p._sb),p._sb=[];else do p.__d=!1,j&&j(n),d=p.render(p.props,p.state,p.context),p.state=p.__s;while(p.__d&&++M<25);p.state=p.__s,p.getChildContext!=null&&(i=v(v({},i),p.getChildContext())),D&&!m&&p.getSnapshotBeforeUpdate!=null&&(x=p.getSnapshotBeforeUpdate(g,b)),N=d!=null&&d.type===S&&d.key==null?L(d.props.children):d,c=k(e,_(N)?N:[N],n,r,i,a,o,s,c,l,u),p.base=n.__e,n.__u&=-161,p.__h.length&&s.push(p),T&&(p.__E=p.__=null)}catch(e){if(s.length=f,n.__v=null,l||o!=null){if(e.then){for(n.__u|=l?160:128;c&&c.nodeType==8&&c.nextSibling;)c=c.nextSibling;o!=null&&(o[o.indexOf(c)]=null),n.__e=c}else if(o!=null)for(P=o.length;P--;)y(o[P])}else n.__e=r.__e;n.__k??=r.__k||[],e.then||ee(n),t.__e(e,n,r)}}else o==null&&n.__v==r.__v?(n.__k=r.__k,n.__e=r.__e):c=n.__e=ne(r.__e,n,r,i,a,o,s,l,u);return(d=t.diffed)&&d(n),128&n.__u?void 0:c}function ee(e){e&&(e.__c&&(e.__c.__e=!0),e.__k&&e.__k.some(ee))}function te(e,n,r){for(var i=0;i<r.length;i++)R(r[i],r[++i],r[++i]);t.__c&&t.__c(n,e),e.some(function(n){try{e=n.__h,n.__h=[],e.some(function(e){e.call(n)})}catch(e){t.__e(e,n.__v)}})}function L(e){return typeof e!=`object`||!e||e.__b>0?e:_(e)?e.map(L):e.constructor===void 0?v({},e):null}function ne(n,r,i,a,o,s,c,l,u){var d,f,p,h,g,v,b,x=i.props||m,S=r.props,C=r.type;if(C==`svg`?o=`http://www.w3.org/2000/svg`:C==`math`?o=`http://www.w3.org/1998/Math/MathML`:o||=`http://www.w3.org/1999/xhtml`,s!=null){for(d=0;d<s.length;d++)if((g=s[d])&&`setAttribute`in g==!!C&&(C?g.localName==C:g.nodeType==3)){n=g,s[d]=null;break}}if(n==null){if(C==null)return document.createTextNode(S);n=document.createElementNS(o,C,S.is&&S),l&&=(t.__m&&t.__m(r,s),!1),s=null}if(C==null)x===S||l&&n.data==S||(n.data=S);else{if(s=C==`textarea`&&S.defaultValue!=null?null:s&&e.call(n.childNodes),!l&&s!=null)for(x={},d=0;d<n.attributes.length;d++)x[(g=n.attributes[d]).name]=g.value;for(d in x)g=x[d],d==`dangerouslySetInnerHTML`?p=g:d==`children`||d in S||d==`value`&&`defaultValue`in S||d==`checked`&&`defaultChecked`in S||P(n,d,null,g,o);for(d in S)g=S[d],d==`children`?h=g:d==`dangerouslySetInnerHTML`?f=g:d==`value`?v=g:d==`checked`?b=g:l&&typeof g!=`function`||x[d]===g||P(n,d,g,x[d],o);if(f)l||p&&(f.__html==p.__html||f.__html==n.innerHTML)||(n.innerHTML=f.__html),r.__k=[];else if(p&&(n.innerHTML=``),k(r.type==`template`?n.content:n,_(h)?h:[h],r,i,a,C==`foreignObject`?`http://www.w3.org/1999/xhtml`:o,s,c,s?s[0]:i.__k&&w(i,0),l,u),s!=null)for(d=s.length;d--;)y(s[d]);l&&C!=`textarea`||(d=`value`,C==`progress`&&v==null?n.removeAttribute(`value`):v!=null&&(v!==n[d]||C==`progress`&&!v||C==`option`&&v!=x[d])&&P(n,d,v,x[d],o),d=`checked`,b!=null&&b!=n[d]&&P(n,d,b,x[d],o))}return n}function R(e,n,r){try{if(typeof e==`function`){var i=typeof e.__u==`function`;i&&e.__u(),i&&n==null||(e.__u=e(n))}else e.current=n}catch(e){t.__e(e,r)}}function re(e,n,r){var i,a;if(t.unmount&&t.unmount(e),(i=e.ref)&&(i.current&&i.current!=e.__e||R(i,null,n)),(i=e.__c)!=null){if(i.componentWillUnmount)try{i.componentWillUnmount()}catch(e){t.__e(e,n)}i.base=i.__P=i.__n=null}if(i=e.__k)for(a=0;a<i.length;a++)i[a]&&re(i[a],n,r||typeof e.type!=`function`);r||y(e.__e),e.__c=e.__=e.__e=void 0}function ie(e,t,n){return this.constructor(e,n)}function ae(n,r,i){var a,o,s,c;r==document&&(r=document.documentElement),t.__&&t.__(n,r),o=(a=typeof i==`function`)?null:i&&i.__k||r.__k,s=[],c=[],I(r,n=(!a&&i||r).__k=b(S,null,[n]),o||m,m,r.namespaceURI,!a&&i?[i]:o?null:r.firstChild?e.call(r.childNodes):null,s,!a&&i?i:o?o.__e:r.firstChild,a,c),te(s,n,c),n.props.children=null}e=h.slice,t={__e:function(e,t,n,r){for(var i,a,o;t=t.__;)if((i=t.__c)&&!i.__)try{if((a=i.constructor)&&a.getDerivedStateFromError!=null&&(i.setState(a.getDerivedStateFromError(e)),o=i.__d),i.componentDidCatch!=null&&(i.componentDidCatch(e,r||{}),o=i.__d),o)return i.__E=i}catch(t){e=t}throw e}},n=0,C.prototype.setState=function(e,t){var n=this.__s!=null&&this.__s!=this.state?this.__s:this.__s=v({},this.state);typeof e==`function`&&(e=e(v({},n),this.props)),e&&v(n,e),e!=null&&this.__v&&(t&&this._sb.push(t),D(this))},C.prototype.forceUpdate=function(e){this.__v&&(this.__e=!0,e&&this.__h.push(e),D(this))},C.prototype.render=S,r=[],a=typeof Promise==`function`?Promise.prototype.then.bind(Promise.resolve()):setTimeout,o=function(e,t){return e.__v.__b-t.__v.__b},O.__r=0,s=Math.random().toString(8),c=`__d`+s,l=`__a`+s,u=/(PointerCapture)$|Capture$/i,d=0,f=F(!1),p=F(!0);var z,B,V,H,U=0,W=[],G=t,K=G.__b,q=G.__r,J=G.diffed,oe=G.__c,se=G.unmount,ce=G.__;function le(e,t){G.__h&&G.__h(B,e,U||t),U=0;var n=B.__H||(B.__H={__:[],__h:[]});return e>=n.__.length&&n.__.push({}),n.__[e]}function Y(e){return U=1,ue(he,e)}function ue(e,t,n){var r=le(z++,2);if(r.t=e,!r.__c&&(r.__=[n?n(t):he(void 0,t),function(e){var t=r.__N?r.__N[0]:r.__[0],n=r.t(t,e);t!==n&&(r.__N=[n,r.__[1]],r.__c.setState({}))}],r.__c=B,!B.__f)){var i=function(e,t,n){if(!r.__c.__H)return!0;var i=!1,o=r.__c.props!==e;if(r.__c.__H.__.some(function(e){if(e.__N){i=!0;var t=e.__[0];e.__=e.__N,e.__N=void 0,t!==e.__[0]&&(o=!0)}}),a){var s=a.call(this,e,t,n);return i?s||o:s}return!i||o};B.__f=!0;var a=B.shouldComponentUpdate,o=B.componentWillUpdate;B.componentWillUpdate=function(e,t,n){if(this.__e){var r=a;a=void 0,i(e,t,n),a=r}o&&o.call(this,e,t,n)},B.shouldComponentUpdate=i}return r.__N||r.__}function de(e,t){var n=le(z++,3);!G.__s&&me(n.__H,t)&&(n.__=e,n.u=t,B.__H.__h.push(n))}function fe(){for(var e;e=W.shift();){var t=e.__H;if(e.__P&&t)try{t.__h.some(Z),t.__h.some(Q),t.__h=[]}catch(n){t.__h=[],G.__e(n,e.__v)}}}G.__b=function(e){B=null,K&&K(e)},G.__=function(e,t){e&&t.__k&&t.__k.__m&&(e.__m=t.__k.__m),ce&&ce(e,t)},G.__r=function(e){q&&q(e),z=0;var t=(B=e.__c).__H;t&&(V===B?(t.__h=[],B.__h=[],t.__.some(function(e){e.__N&&(e.__=e.__N),e.u=e.__N=void 0})):(t.__h.some(Z),t.__h.some(Q),t.__h=[],z=0)),V=B},G.diffed=function(e){J&&J(e);var t=e.__c;t&&t.__H&&(t.__H.__h.length&&(W.push(t)!==1&&H===G.requestAnimationFrame||((H=G.requestAnimationFrame)||pe)(fe)),t.__H.__.some(function(e){e.u&&=(e.__H=e.u,void 0)})),V=B=null},G.__c=function(e,t){t.some(function(e){try{e.__h.some(Z),e.__h=e.__h.filter(function(e){return!e.__||Q(e)})}catch(n){t.some(function(e){e.__h&&=[]}),t=[],G.__e(n,e.__v)}}),oe&&oe(e,t)},G.unmount=function(e){se&&se(e);var t,n=e.__c;n&&n.__H&&(n.__H.__.some(function(e){try{Z(e)}catch(e){t=e}}),n.__H=void 0,t&&G.__e(t,n.__v))};var X=typeof requestAnimationFrame==`function`;function pe(e){var t,n=function(){clearTimeout(r),X&&cancelAnimationFrame(t),setTimeout(e)},r=setTimeout(n,35);X&&(t=requestAnimationFrame(n))}function Z(e){var t=B,n=e.__c;typeof n==`function`&&(e.__c=void 0,n()),B=t}function Q(e){var t=B;e.__c=e.__(),B=t}function me(e,t){return!e||e.length!==t.length||t.some(function(t,n){return t!==e[n]})}function he(e,t){return typeof t==`function`?t(e):t}var ge=[`queued`,`skipped`,`done`,`error`],_e=async(e,t={})=>{let n=await fetch(e,{...t,redirect:`manual`,headers:{Accept:`application/json`,...t.headers}});if(n.type===`opaqueredirect`)throw location.reload(),Error(`session expired`);let r=await n.json().catch(()=>null);if(!n.ok)throw Error(r?.error??`HTTP ${n.status}`);return r},ve={me:()=>_e(`/api/v1/me`),queues:()=>_e(`/api/v1/queues`)},ye=0;Array.isArray;function $(e,n,r,i,a,o){n||={};var s,c,l=n;if(`ref`in l)for(c in l={},n)c==`ref`?s=n[c]:l[c]=n[c];var u={type:e,props:l,key:r,ref:s,__k:null,__:null,__b:0,__e:null,__c:null,constructor:void 0,__v:--ye,__i:-1,__u:0,__source:a,__self:o};if(typeof e==`function`&&(s=e.defaultProps))for(c in s)l[c]===void 0&&(l[c]=s[c]);return t.vnode&&t.vnode(u),u}ae($(()=>{let[e,t]=Y(null),[n,r]=Y(null),[i,a]=Y(null);return de(()=>{Promise.all([ve.me(),ve.queues()]).then(([e,n])=>{t(e),r(n)}).catch(e=>a(e instanceof Error?e.message:String(e)))},[]),$(`main`,{children:[$(`header`,{children:[$(`h1`,{children:`Print Backlog`}),$(`span`,{class:`muted`,children:e?`${e.user} · v${e.version}`:``})]}),i&&$(`p`,{class:`error`,children:i}),n?.map(e=>$(`section`,{class:`queue`,children:[$(`h2`,{children:e.name}),$(`ul`,{class:`counts`,children:ge.map(t=>$(`li`,{class:`status-${t}`,children:[t,` `,$(`b`,{children:e.counts[t]})]},t))})]},e.id))]})},{}),document.getElementById(`app`));
@@ -0,0 +1 @@
1
+ :root{--bg:#fff;--fg:#0f172a;--muted:#64748b;--line:#e2e8f0;--accent:#2563eb;--error:#dc2626;color:var(--fg);background:var(--bg);font-family:system-ui,sans-serif}@media (prefers-color-scheme:dark){:root{--bg:#0b1120;--fg:#e2e8f0;--muted:#94a3b8;--line:#1e293b;--accent:#60a5fa;--error:#f87171}}body{background:var(--bg);margin:0}main{max-width:60rem;margin:0 auto;padding:1rem}header{border-bottom:1px solid var(--line);justify-content:space-between;align-items:baseline;gap:1rem;display:flex}h1{font-size:1.25rem}h2{font-size:1rem}.muted{color:var(--muted)}.error{color:var(--error)}.counts{flex-wrap:wrap;gap:.5rem;padding:0;list-style:none;display:flex}.counts li{border:1px solid var(--line);border-radius:999px;padding:.2rem .7rem}
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><rect x="4" y="6" width="24" height="5" rx="1.5" fill="#3b82f6"/><rect x="4" y="14" width="24" height="5" rx="1.5" fill="#64748b"/><rect x="4" y="22" width="24" height="5" rx="1.5" fill="#64748b"/></svg>
@@ -0,0 +1,15 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <meta name="color-scheme" content="light dark" />
7
+ <title>Print Backlog</title>
8
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
9
+ <script type="module" crossorigin src="/assets/index-CLP1jBWd.js"></script>
10
+ <link rel="stylesheet" crossorigin href="/assets/index-DxpzozYw.css">
11
+ </head>
12
+ <body>
13
+ <div id="app"></div>
14
+ </body>
15
+ </html>
@@ -0,0 +1,12 @@
1
+ import type { Me } from "../../shared/types.js";
2
+ import type { Ctx } from "../http.js";
3
+ import type { Sql } from "../db.js";
4
+ import { Reply } from "../http.js";
5
+ /** The last Bambuddy check, refreshed in the background (cli.ts), never per probe. */
6
+ export interface BambuddyState {
7
+ configured: boolean;
8
+ ok: boolean | null;
9
+ }
10
+ export declare const health: (sql: Sql, bambuddy: BambuddyState) => () => Promise<Reply>;
11
+ export declare const me: (bambuddyConfigured: boolean) => ({ user }: Ctx) => Me;
12
+ //# sourceMappingURL=health.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.d.ts","sourceRoot":"","sources":["../../../src/server/api/health.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAU,EAAE,EAAE,MAAM,uBAAuB,CAAC;AACxD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AACtC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAGnC,sFAAsF;AACtF,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,OAAO,CAAC;IACpB,EAAE,EAAE,OAAO,GAAG,IAAI,CAAC;CACpB;AAED,eAAO,MAAM,MAAM,GAChB,KAAK,GAAG,EAAE,UAAU,aAAa,WAAe,OAAO,CAAC,KAAK,CAoB7D,CAAC;AAEJ,eAAO,MAAM,EAAE,GACZ,oBAAoB,OAAO,MAC3B,UAAU,GAAG,KAAG,EAIf,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { Reply } from "../http.js";
2
+ import { VERSION } from "../version.js";
3
+ export const health = (sql, bambuddy) => async () => {
4
+ let db = "ok";
5
+ try {
6
+ await sql `SELECT 1`;
7
+ }
8
+ catch {
9
+ db = "error";
10
+ }
11
+ const bb = !bambuddy.configured
12
+ ? "off"
13
+ : bambuddy.ok === false
14
+ ? "unreachable"
15
+ : "ok";
16
+ const body = {
17
+ ok: db === "ok",
18
+ db,
19
+ bambuddy: bb,
20
+ version: VERSION,
21
+ };
22
+ // Only the database makes the app unusable; Bambuddy is shown, not fatal.
23
+ return new Reply(db === "ok" ? 200 : 503, body);
24
+ };
25
+ export const me = (bambuddyConfigured) => ({ user }) => ({
26
+ user,
27
+ version: VERSION,
28
+ bambuddy: bambuddyConfigured,
29
+ });
30
+ //# sourceMappingURL=health.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"health.js","sourceRoot":"","sources":["../../../src/server/api/health.ts"],"names":[],"mappings":"AAGA,OAAO,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AACnC,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAQxC,MAAM,CAAC,MAAM,MAAM,GACjB,CAAC,GAAQ,EAAE,QAAuB,EAAE,EAAE,CAAC,KAAK,IAAoB,EAAE;IAChE,IAAI,EAAE,GAAiB,IAAI,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,GAAG,CAAA,UAAU,CAAC;IACtB,CAAC;IAAC,MAAM,CAAC;QACP,EAAE,GAAG,OAAO,CAAC;IACf,CAAC;IACD,MAAM,EAAE,GAAuB,CAAC,QAAQ,CAAC,UAAU;QACjD,CAAC,CAAC,KAAK;QACP,CAAC,CAAC,QAAQ,CAAC,EAAE,KAAK,KAAK;YACrB,CAAC,CAAC,aAAa;YACf,CAAC,CAAC,IAAI,CAAC;IACX,MAAM,IAAI,GAAW;QACnB,EAAE,EAAE,EAAE,KAAK,IAAI;QACf,EAAE;QACF,QAAQ,EAAE,EAAE;QACZ,OAAO,EAAE,OAAO;KACjB,CAAC;IACF,0EAA0E;IAC1E,OAAO,IAAI,KAAK,CAAC,EAAE,KAAK,IAAI,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,IAAI,CAAC,CAAC;AAClD,CAAC,CAAC;AAEJ,MAAM,CAAC,MAAM,EAAE,GACb,CAAC,kBAA2B,EAAE,EAAE,CAChC,CAAC,EAAE,IAAI,EAAO,EAAM,EAAE,CAAC,CAAC;IACtB,IAAI;IACJ,OAAO,EAAE,OAAO;IAChB,QAAQ,EAAE,kBAAkB;CAC7B,CAAC,CAAC"}
@@ -0,0 +1,9 @@
1
+ import type { HookAck } from "../../shared/types.js";
2
+ import type { Sql } from "../db.js";
3
+ import type { Ctx } from "../http.js";
4
+ /**
5
+ * Store the event. Matching it to an item and applying the status rule comes
6
+ * with PB6 (docs/plan.md §3.9); until then every event stays open.
7
+ */
8
+ export declare const bambuddyHook: (sql: Sql) => ({ body }: Ctx) => Promise<HookAck>;
9
+ //# sourceMappingURL=hooks.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.d.ts","sourceRoot":"","sources":["../../../src/server/api/hooks.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,uBAAuB,CAAC;AACrD,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AACpC,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,YAAY,CAAC;AAiBtC;;;GAGG;AACH,eAAO,MAAM,YAAY,GACtB,KAAK,GAAG,MACF,UAAU,GAAG,KAAG,OAAO,CAAC,OAAO,CAQrC,CAAC"}
@@ -0,0 +1,29 @@
1
+ import { z } from "zod";
2
+ /**
3
+ * Bambuddy's "Webhook" notification provider, payload format "generic":
4
+ * {title, message, timestamp, source, event, printer, filename, status?,
5
+ * duration?, filament_grams?, reason?, …}. Only the fields the matching needs
6
+ * are typed; the whole payload is kept as it came.
7
+ */
8
+ const payloadSchema = z
9
+ .object({
10
+ event: z.string().min(1).max(100),
11
+ filename: z.string().max(1000).optional(),
12
+ printer: z.string().max(200).optional(),
13
+ status: z.string().max(100).optional(),
14
+ })
15
+ .loose();
16
+ /**
17
+ * Store the event. Matching it to an item and applying the status rule comes
18
+ * with PB6 (docs/plan.md §3.9); until then every event stays open.
19
+ */
20
+ export const bambuddyHook = (sql) => async ({ body }) => {
21
+ const p = payloadSchema.parse(body);
22
+ const [row] = await sql `
23
+ INSERT INTO backlog.print_event (event, filename, printer, status, payload)
24
+ VALUES (${p.event}, ${p.filename ?? null}, ${p.printer ?? null}, ${p.status ?? null}, ${sql.json(body)})
25
+ RETURNING id
26
+ `;
27
+ return { stored: true, id: Number(row.id) };
28
+ };
29
+ //# sourceMappingURL=hooks.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"hooks.js","sourceRoot":"","sources":["../../../src/server/api/hooks.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAKxB;;;;;GAKG;AACH,MAAM,aAAa,GAAG,CAAC;KACpB,MAAM,CAAC;IACN,KAAK,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,GAAG,CAAC;IACjC,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,EAAE;IACzC,OAAO,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;IACvC,MAAM,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,QAAQ,EAAE;CACvC,CAAC;KACD,KAAK,EAAE,CAAC;AAEX;;;GAGG;AACH,MAAM,CAAC,MAAM,YAAY,GACvB,CAAC,GAAQ,EAAE,EAAE,CACb,KAAK,EAAE,EAAE,IAAI,EAAO,EAAoB,EAAE;IACxC,MAAM,CAAC,GAAG,aAAa,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpC,MAAM,CAAC,GAAG,CAAC,GAAG,MAAM,GAAG,CAAkB;;gBAE7B,CAAC,CAAC,KAAK,KAAK,CAAC,CAAC,QAAQ,IAAI,IAAI,KAAK,CAAC,CAAC,OAAO,IAAI,IAAI,KAAK,CAAC,CAAC,MAAM,IAAI,IAAI,KAAK,GAAG,CAAC,IAAI,CAAC,IAAa,CAAC;;KAEhH,CAAC;IACF,OAAO,EAAE,MAAM,EAAE,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,GAAI,CAAC,EAAE,CAAC,EAAE,CAAC;AAC/C,CAAC,CAAC"}
@@ -0,0 +1,5 @@
1
+ import { type QueueSummary } from "../../shared/types.js";
2
+ import type { Sql } from "../db.js";
3
+ /** GET /api/v1/queues: every queue with its item counts per status (deleted items excluded). */
4
+ export declare const listQueues: (sql: Sql) => () => Promise<QueueSummary[]>;
5
+ //# sourceMappingURL=queues.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queues.d.ts","sourceRoot":"","sources":["../../../src/server/api/queues.ts"],"names":[],"mappings":"AAAA,OAAO,EAGL,KAAK,YAAY,EAClB,MAAM,uBAAuB,CAAC;AAC/B,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,UAAU,CAAC;AAEpC,gGAAgG;AAChG,eAAO,MAAM,UAAU,GAAI,KAAK,GAAG,WAAe,OAAO,CAAC,YAAY,EAAE,CAqCvE,CAAC"}
@@ -0,0 +1,30 @@
1
+ import { ITEM_STATUSES, } from "../../shared/types.js";
2
+ /** GET /api/v1/queues: every queue with its item counts per status (deleted items excluded). */
3
+ export const listQueues = (sql) => async () => {
4
+ const rows = await sql `
5
+ SELECT q.id, q.name, q.position, q.auto_status, i.status, count(i.id) AS n
6
+ FROM backlog.queue q
7
+ LEFT JOIN backlog.item i ON i.queue_id = q.id AND i.deleted_at IS NULL
8
+ GROUP BY q.id, i.status
9
+ ORDER BY q.position, q.id
10
+ `;
11
+ const byId = new Map();
12
+ for (const r of rows) {
13
+ const id = Number(r.id);
14
+ let q = byId.get(id);
15
+ if (!q) {
16
+ q = {
17
+ id,
18
+ name: r.name,
19
+ position: Number(r.position),
20
+ autoStatus: r.auto_status,
21
+ counts: Object.fromEntries(ITEM_STATUSES.map((s) => [s, 0])),
22
+ };
23
+ byId.set(id, q);
24
+ }
25
+ if (r.status)
26
+ q.counts[r.status] = Number(r.n);
27
+ }
28
+ return [...byId.values()];
29
+ };
30
+ //# sourceMappingURL=queues.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"queues.js","sourceRoot":"","sources":["../../../src/server/api/queues.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,aAAa,GAGd,MAAM,uBAAuB,CAAC;AAG/B,gGAAgG;AAChG,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAQ,EAAE,EAAE,CAAC,KAAK,IAA6B,EAAE;IAC1E,MAAM,IAAI,GAAG,MAAM,GAAG,CASrB;;;;;;GAMA,CAAC;IACF,MAAM,IAAI,GAAG,IAAI,GAAG,EAAwB,CAAC;IAC7C,KAAK,MAAM,CAAC,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACxB,IAAI,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC,CAAC;QACrB,IAAI,CAAC,CAAC,EAAE,CAAC;YACP,CAAC,GAAG;gBACF,EAAE;gBACF,IAAI,EAAE,CAAC,CAAC,IAAI;gBACZ,QAAQ,EAAE,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;gBAC5B,UAAU,EAAE,CAAC,CAAC,WAAW;gBACzB,MAAM,EAAE,MAAM,CAAC,WAAW,CAAC,aAAa,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAG1D;aACF,CAAC;YACF,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,IAAI,CAAC,CAAC,MAAM;YAAE,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,GAAG,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IACjD,CAAC;IACD,OAAO,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,CAAC,CAAC;AAC5B,CAAC,CAAC"}
@@ -0,0 +1,10 @@
1
+ import { type BambuddyState } from "./api/health.js";
2
+ import type { Sql } from "./db.js";
3
+ import { Router } from "./http.js";
4
+ export interface AppDeps {
5
+ sql: Sql;
6
+ bambuddy: BambuddyState;
7
+ }
8
+ /** Every route. Paths are the contract with Caddy's vhost: only /health and /hooks/bambuddy bypass SSO. */
9
+ export declare const buildRouter: ({ sql, bambuddy }: AppDeps) => Router;
10
+ //# sourceMappingURL=app.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.d.ts","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,aAAa,EAAc,MAAM,iBAAiB,CAAC;AAEjE,OAAO,KAAK,EAAE,GAAG,EAAE,MAAM,SAAS,CAAC;AACnC,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAEnC,MAAM,WAAW,OAAO;IACtB,GAAG,EAAE,GAAG,CAAC;IACT,QAAQ,EAAE,aAAa,CAAC;CACzB;AAED,2GAA2G;AAC3G,eAAO,MAAM,WAAW,GAAI,mBAAmB,OAAO,KAAG,MAKd,CAAC"}
@@ -0,0 +1,11 @@
1
+ import { bambuddyHook } from "./api/hooks.js";
2
+ import { health, me } from "./api/health.js";
3
+ import { listQueues } from "./api/queues.js";
4
+ import { Router } from "./http.js";
5
+ /** Every route. Paths are the contract with Caddy's vhost: only /health and /hooks/bambuddy bypass SSO. */
6
+ export const buildRouter = ({ sql, bambuddy }) => new Router()
7
+ .get("/health", health(sql, bambuddy), { access: "public" })
8
+ .post("/hooks/bambuddy", bambuddyHook(sql), { access: "webhook" })
9
+ .get("/api/v1/me", me(bambuddy.configured))
10
+ .get("/api/v1/queues", listQueues(sql));
11
+ //# sourceMappingURL=app.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"app.js","sourceRoot":"","sources":["../../src/server/app.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAsB,MAAM,EAAE,EAAE,EAAE,MAAM,iBAAiB,CAAC;AACjE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAE7C,OAAO,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAOnC,2GAA2G;AAC3G,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,EAAE,GAAG,EAAE,QAAQ,EAAW,EAAU,EAAE,CAChE,IAAI,MAAM,EAAE;KACT,GAAG,CAAC,SAAS,EAAE,MAAM,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,EAAE,MAAM,EAAE,QAAQ,EAAE,CAAC;KAC3D,IAAI,CAAC,iBAAiB,EAAE,YAAY,CAAC,GAAG,CAAC,EAAE,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;KACjE,GAAG,CAAC,YAAY,EAAE,EAAE,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC;KAC1C,GAAG,CAAC,gBAAgB,EAAE,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC"}
@@ -0,0 +1,11 @@
1
+ import type { IncomingMessage } from "node:http";
2
+ /**
3
+ * Who is calling. Browser routes: the SSO user Caddy injected
4
+ * (TRUST_USER_HEADER). A request without it means the gate in front is broken
5
+ * or bypassed, so it fails closed (500), unless DEV_USER is set (development
6
+ * against a loopback origin only, config.ts).
7
+ */
8
+ export declare const ssoUser: (req: IncomingMessage, header: string, devUser: string | null) => string | null;
9
+ /** Constant-time `Authorization: Bearer <token>` check for the webhook. */
10
+ export declare const bearerMatches: (req: IncomingMessage, token: string) => boolean;
11
+ //# sourceMappingURL=auth.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.d.ts","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,WAAW,CAAC;AAEjD;;;;;GAKG;AACH,eAAO,MAAM,OAAO,GAClB,KAAK,eAAe,EACpB,QAAQ,MAAM,EACd,SAAS,MAAM,GAAG,IAAI,KACrB,MAAM,GAAG,IAKX,CAAC;AAEF,2EAA2E;AAC3E,eAAO,MAAM,aAAa,GAAI,KAAK,eAAe,EAAE,OAAO,MAAM,KAAG,OAanE,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+ /**
3
+ * Who is calling. Browser routes: the SSO user Caddy injected
4
+ * (TRUST_USER_HEADER). A request without it means the gate in front is broken
5
+ * or bypassed, so it fails closed (500), unless DEV_USER is set (development
6
+ * against a loopback origin only, config.ts).
7
+ */
8
+ export const ssoUser = (req, header, devUser) => {
9
+ const raw = req.headers[header];
10
+ const value = (Array.isArray(raw) ? raw[0] : raw)?.trim();
11
+ if (value)
12
+ return value;
13
+ return devUser;
14
+ };
15
+ /** Constant-time `Authorization: Bearer <token>` check for the webhook. */
16
+ export const bearerMatches = (req, token) => {
17
+ const header = req.headers.authorization ?? "";
18
+ const m = /^Bearer\s+(.+)$/i.exec(header.trim());
19
+ if (!m)
20
+ return false;
21
+ const given = Buffer.from(m[1].trim(), "utf8");
22
+ const expected = Buffer.from(token, "utf8");
23
+ // timingSafeEqual needs equal lengths; compare against itself to keep the
24
+ // time independent of where a shorter token differs.
25
+ if (given.length !== expected.length) {
26
+ timingSafeEqual(expected, expected);
27
+ return false;
28
+ }
29
+ return timingSafeEqual(given, expected);
30
+ };
31
+ //# sourceMappingURL=auth.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"auth.js","sourceRoot":"","sources":["../../src/server/auth.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,aAAa,CAAC;AAG9C;;;;;GAKG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CACrB,GAAoB,EACpB,MAAc,EACd,OAAsB,EACP,EAAE;IACjB,MAAM,GAAG,GAAG,GAAG,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;IAChC,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,EAAE,IAAI,EAAE,CAAC;IAC1D,IAAI,KAAK;QAAE,OAAO,KAAK,CAAC;IACxB,OAAO,OAAO,CAAC;AACjB,CAAC,CAAC;AAEF,2EAA2E;AAC3E,MAAM,CAAC,MAAM,aAAa,GAAG,CAAC,GAAoB,EAAE,KAAa,EAAW,EAAE;IAC5E,MAAM,MAAM,GAAG,GAAG,CAAC,OAAO,CAAC,aAAa,IAAI,EAAE,CAAC;IAC/C,MAAM,CAAC,GAAG,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,IAAI,EAAE,CAAC,CAAC;IACjD,IAAI,CAAC,CAAC;QAAE,OAAO,KAAK,CAAC;IACrB,MAAM,KAAK,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,EAAE,MAAM,CAAC,CAAC;IAChD,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,KAAK,EAAE,MAAM,CAAC,CAAC;IAC5C,0EAA0E;IAC1E,qDAAqD;IACrD,IAAI,KAAK,CAAC,MAAM,KAAK,QAAQ,CAAC,MAAM,EAAE,CAAC;QACrC,eAAe,CAAC,QAAQ,EAAE,QAAQ,CAAC,CAAC;QACpC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,eAAe,CAAC,KAAK,EAAE,QAAQ,CAAC,CAAC;AAC1C,CAAC,CAAC"}
@@ -0,0 +1,15 @@
1
+ export interface BambuddyClient {
2
+ /** Reachable with the API key? Never throws. */
3
+ ping(): Promise<{
4
+ ok: boolean;
5
+ detail: string;
6
+ }>;
7
+ }
8
+ /**
9
+ * Bambuddy's API, at BAMBUDDY_URL (https://bambuddy.mxpicture.de: vps1's
10
+ * Caddy lets this host's calls with an X-API-Key through without SSO, for the
11
+ * library endpoints and /api/v1/auth/status only). Upload and folder lookup
12
+ * come with Send to Bambuddy (PB6).
13
+ */
14
+ export declare const createBambuddyClient: (url: string, apiKey: string, fetchImpl?: typeof fetch) => BambuddyClient;
15
+ //# sourceMappingURL=bambuddy.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bambuddy.d.ts","sourceRoot":"","sources":["../../src/server/bambuddy.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,gDAAgD;IAChD,IAAI,IAAI,OAAO,CAAC;QAAE,EAAE,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAClD;AAED;;;;;GAKG;AACH,eAAO,MAAM,oBAAoB,GAC/B,KAAK,MAAM,EACX,QAAQ,MAAM,EACd,YAAW,OAAO,KAAa,KAC9B,cAoBD,CAAC"}
@@ -0,0 +1,31 @@
1
+ import { errorMessage } from "./log.js";
2
+ /**
3
+ * Bambuddy's API, at BAMBUDDY_URL (https://bambuddy.mxpicture.de: vps1's
4
+ * Caddy lets this host's calls with an X-API-Key through without SSO, for the
5
+ * library endpoints and /api/v1/auth/status only). Upload and folder lookup
6
+ * come with Send to Bambuddy (PB6).
7
+ */
8
+ export const createBambuddyClient = (url, apiKey, fetchImpl = fetch) => ({
9
+ async ping() {
10
+ try {
11
+ const res = await fetchImpl(`${url}/api/v1/auth/status`, {
12
+ headers: { "X-API-Key": apiKey, Accept: "application/json" },
13
+ redirect: "manual",
14
+ signal: AbortSignal.timeout(10_000),
15
+ });
16
+ // A redirect is the SSO portal: the vps1 route did not let us through.
17
+ if (res.status >= 300 && res.status < 400)
18
+ return {
19
+ ok: false,
20
+ detail: `redirected (${res.status}): SSO gate, not the API route`,
21
+ };
22
+ if (!res.ok)
23
+ return { ok: false, detail: `HTTP ${res.status}` };
24
+ return { ok: true, detail: "ok" };
25
+ }
26
+ catch (e) {
27
+ return { ok: false, detail: errorMessage(e) };
28
+ }
29
+ },
30
+ });
31
+ //# sourceMappingURL=bambuddy.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bambuddy.js","sourceRoot":"","sources":["../../src/server/bambuddy.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,UAAU,CAAC;AAOxC;;;;;GAKG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAClC,GAAW,EACX,MAAc,EACd,YAA0B,KAAK,EACf,EAAE,CAAC,CAAC;IACpB,KAAK,CAAC,IAAI;QACR,IAAI,CAAC;YACH,MAAM,GAAG,GAAG,MAAM,SAAS,CAAC,GAAG,GAAG,qBAAqB,EAAE;gBACvD,OAAO,EAAE,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,EAAE;gBAC5D,QAAQ,EAAE,QAAQ;gBAClB,MAAM,EAAE,WAAW,CAAC,OAAO,CAAC,MAAM,CAAC;aACpC,CAAC,CAAC;YACH,uEAAuE;YACvE,IAAI,GAAG,CAAC,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,GAAG,GAAG;gBACvC,OAAO;oBACL,EAAE,EAAE,KAAK;oBACT,MAAM,EAAE,eAAe,GAAG,CAAC,MAAM,gCAAgC;iBAClE,CAAC;YACJ,IAAI,CAAC,GAAG,CAAC,EAAE;gBAAE,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,QAAQ,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC;YAChE,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC;QACpC,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACX,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,YAAY,CAAC,CAAC,CAAC,EAAE,CAAC;QAChD,CAAC;IACH,CAAC;CACF,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env node
2
+ export {};
3
+ //# sourceMappingURL=cli.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../src/server/cli.ts"],"names":[],"mappings":""}
@@ -0,0 +1,123 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * print-backlog: a backlog for unsliced 3D models next to Bambuddy
4
+ * (docs/plan.md). Deployed on mxpicture.de by ansible-playbook-contabo-vps
5
+ * (roles/print_backlog), behind Caddy's SSO.
6
+ *
7
+ * serve apply the schema, then the HTTP server (the container's command)
8
+ * setup apply the schema only (migrations + the default queue)
9
+ * check validate the config, reach the database and Bambuddy; exit 1 on a problem
10
+ *
11
+ * Configuration from the environment (config.ts) and the PG* variables.
12
+ */
13
+ import { createServer } from "node:http";
14
+ import { fileURLToPath } from "node:url";
15
+ import { buildRouter } from "./app.js";
16
+ import { createBambuddyClient } from "./bambuddy.js";
17
+ import { loadConfig } from "./config.js";
18
+ import { createSql } from "./db.js";
19
+ import { handle } from "./http.js";
20
+ import { errorMessage, log } from "./log.js";
21
+ import { applySchema } from "./schema.js";
22
+ import { VERSION } from "./version.js";
23
+ /** How often Bambuddy's reachability is re-checked for /health. */
24
+ const BAMBUDDY_CHECK_MS = 5 * 60_000;
25
+ const serve = async () => {
26
+ const config = loadConfig();
27
+ const sql = createSql();
28
+ await applySchema(sql, config.defaultQueue);
29
+ const bambuddy = {
30
+ configured: config.bambuddy !== null,
31
+ ok: null,
32
+ };
33
+ const client = config.bambuddy
34
+ ? createBambuddyClient(config.bambuddy.url, config.bambuddy.apiKey)
35
+ : null;
36
+ const checkBambuddy = async () => {
37
+ if (!client)
38
+ return;
39
+ const r = await client.ping();
40
+ if (r.ok !== bambuddy.ok)
41
+ log(`bambuddy ${r.ok ? "reachable" : `unreachable: ${r.detail}`}`);
42
+ bambuddy.ok = r.ok;
43
+ };
44
+ void checkBambuddy();
45
+ const timer = client
46
+ ? setInterval(() => void checkBambuddy(), BAMBUDDY_CHECK_MS)
47
+ : null;
48
+ const staticDir = fileURLToPath(new URL("../public/", import.meta.url));
49
+ const router = buildRouter({ sql, bambuddy });
50
+ const server = createServer((req, res) => void handle(req, res, {
51
+ origin: config.origin,
52
+ userHeader: config.userHeader,
53
+ devUser: config.devUser,
54
+ webhookToken: config.webhookToken,
55
+ router,
56
+ staticDir,
57
+ log,
58
+ }));
59
+ // Uploads of a few hundred MB from a home uplink take a while.
60
+ server.requestTimeout = 30 * 60_000;
61
+ server.keepAliveTimeout = 65_000;
62
+ server.listen(config.port, () => log(`print-backlog ${VERSION} listening on :${config.port} for ${config.origin} ` +
63
+ `(bambuddy ${config.bambuddy ? config.bambuddy.url : "off"}${config.devUser ? `, DEV_USER ${config.devUser}` : ""})`));
64
+ const stop = () => {
65
+ log("stopping");
66
+ if (timer)
67
+ clearInterval(timer);
68
+ server.closeAllConnections();
69
+ server.close(() => void sql.end().finally(() => process.exit(0)));
70
+ };
71
+ process.once("SIGTERM", stop);
72
+ process.once("SIGINT", stop);
73
+ };
74
+ const setup = async () => {
75
+ const config = loadConfig();
76
+ const sql = createSql(1);
77
+ try {
78
+ await applySchema(sql, config.defaultQueue);
79
+ log("schema up to date");
80
+ }
81
+ finally {
82
+ await sql.end();
83
+ }
84
+ };
85
+ const check = async () => {
86
+ const config = loadConfig();
87
+ let failed = false;
88
+ const sql = createSql(1);
89
+ try {
90
+ await sql `SELECT 1`;
91
+ log("database: ok");
92
+ }
93
+ catch (e) {
94
+ failed = true;
95
+ log(`database: ${errorMessage(e)}`);
96
+ }
97
+ finally {
98
+ await sql.end();
99
+ }
100
+ if (config.bambuddy) {
101
+ const r = await createBambuddyClient(config.bambuddy.url, config.bambuddy.apiKey).ping();
102
+ if (!r.ok)
103
+ failed = true;
104
+ log(`bambuddy ${config.bambuddy.url}: ${r.detail}`);
105
+ }
106
+ else {
107
+ log("bambuddy: off (BAMBUDDY_URL empty)");
108
+ }
109
+ if (failed)
110
+ process.exitCode = 1;
111
+ };
112
+ const commands = { serve, setup, check };
113
+ const [command] = process.argv.slice(2);
114
+ const run = command ? commands[command] : undefined;
115
+ if (!run) {
116
+ console.error("usage: print-backlog serve|setup|check");
117
+ process.exit(2);
118
+ }
119
+ run().catch((e) => {
120
+ console.error(errorMessage(e));
121
+ process.exit(1);
122
+ });
123
+ //# sourceMappingURL=cli.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../../src/server/cli.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ export interface Config {
2
+ port: number;
3
+ origin: string;
4
+ dataDir: string;
5
+ /** lower-case, as Node presents header names */
6
+ userHeader: string;
7
+ devUser: string | null;
8
+ webhookToken: string;
9
+ bambuddy: {
10
+ url: string;
11
+ apiKey: string;
12
+ folder: string;
13
+ } | null;
14
+ maxUploadBytes: number;
15
+ defaultQueue: string;
16
+ trashDays: number;
17
+ }
18
+ export declare const loadConfig: (env?: NodeJS.ProcessEnv) => Config;
19
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/server/config.ts"],"names":[],"mappings":"AA2CA,MAAM,WAAW,MAAM;IACrB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,gDAAgD;IAChD,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IACjE,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;CACnB;AAID,eAAO,MAAM,UAAU,GAAI,MAAK,MAAM,CAAC,UAAwB,KAAG,MA4BjE,CAAC"}