vasp-cli 0.1.3 → 0.1.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.
- package/dist/vasp +0 -1
- package/package.json +1 -1
- package/templates/templates/shared/.env.example.hbs +14 -0
- package/templates/templates/shared/.gitignore.hbs +8 -0
- package/templates/templates/shared/auth/client/Login.vue.hbs +46 -0
- package/templates/templates/shared/auth/client/Register.vue.hbs +42 -0
- package/templates/templates/shared/auth/server/index.hbs +51 -0
- package/templates/templates/shared/auth/server/middleware.hbs +33 -0
- package/templates/templates/shared/auth/server/providers/github.hbs +48 -0
- package/templates/templates/shared/auth/server/providers/google.hbs +53 -0
- package/templates/templates/shared/auth/server/providers/usernameAndPassword.hbs +69 -0
- package/templates/templates/shared/bunfig.toml.hbs +2 -0
- package/templates/templates/shared/drizzle/schema.hbs +37 -0
- package/templates/templates/shared/jobs/_job.hbs +24 -0
- package/templates/templates/shared/jobs/boss.hbs +15 -0
- package/templates/templates/shared/package.json.hbs +32 -0
- package/templates/templates/shared/server/db/client.hbs +12 -0
- package/templates/templates/shared/server/index.hbs +52 -0
- package/templates/templates/shared/server/routes/actions/_action.hbs +20 -0
- package/templates/templates/shared/server/routes/crud/_crud.hbs +42 -0
- package/templates/templates/shared/server/routes/jobs/_schedule.hbs +12 -0
- package/templates/templates/shared/server/routes/queries/_query.hbs +20 -0
- package/templates/templates/shared/server/routes/realtime/_channel.hbs +30 -0
- package/templates/templates/shared/server/routes/realtime/index.hbs +9 -0
- package/templates/templates/shared/tsconfig.json.hbs +21 -0
- package/templates/templates/spa/js/index.html.hbs +12 -0
- package/templates/templates/spa/js/src/App.vue.hbs +3 -0
- package/templates/templates/spa/js/src/main.js.hbs +9 -0
- package/templates/templates/spa/js/src/router/index.js.hbs +41 -0
- package/templates/templates/spa/js/src/vasp/auth.js.hbs +45 -0
- package/templates/templates/spa/js/src/vasp/client/actions.js.hbs +15 -0
- package/templates/templates/spa/js/src/vasp/client/crud.js.hbs +30 -0
- package/templates/templates/spa/js/src/vasp/client/index.js.hbs +16 -0
- package/templates/templates/spa/js/src/vasp/client/queries.js.hbs +15 -0
- package/templates/templates/spa/js/src/vasp/client/realtime.js.hbs +51 -0
- package/templates/templates/spa/js/src/vasp/plugin.js.hbs +11 -0
- package/templates/templates/spa/js/vite.config.js.hbs +26 -0
- package/templates/templates/spa/ts/index.html.hbs +12 -0
- package/templates/templates/spa/ts/src/App.vue.hbs +3 -0
- package/templates/templates/spa/ts/src/main.ts.hbs +9 -0
- package/templates/templates/spa/ts/src/router/index.ts.hbs +41 -0
- package/templates/templates/spa/ts/src/vasp/auth.ts.hbs +53 -0
- package/templates/templates/spa/ts/src/vasp/client/actions.ts.hbs +19 -0
- package/templates/templates/spa/ts/src/vasp/client/crud.ts.hbs +37 -0
- package/templates/templates/spa/ts/src/vasp/client/index.ts.hbs +17 -0
- package/templates/templates/spa/ts/src/vasp/client/queries.ts.hbs +19 -0
- package/templates/templates/spa/ts/src/vasp/client/realtime.ts.hbs +56 -0
- package/templates/templates/spa/ts/src/vasp/client/types.ts.hbs +33 -0
- package/templates/templates/spa/ts/src/vasp/plugin.ts.hbs +12 -0
- package/templates/templates/spa/ts/vite.config.ts.hbs +26 -0
- package/templates/templates/ssr/js/_page.vue.hbs +10 -0
- package/templates/templates/ssr/js/app.vue.hbs +3 -0
- package/templates/templates/ssr/js/composables/useAuth.js.hbs +52 -0
- package/templates/templates/ssr/js/composables/useVasp.js.hbs +6 -0
- package/templates/templates/ssr/js/middleware/auth.js.hbs +8 -0
- package/templates/templates/ssr/js/nuxt.config.js.hbs +15 -0
- package/templates/templates/ssr/js/plugins/vasp.client.js.hbs +17 -0
- package/templates/templates/ssr/js/plugins/vasp.server.js.hbs +33 -0
- package/templates/templates/ssr/ts/_page.vue.hbs +10 -0
- package/templates/templates/ssr/ts/app.vue.hbs +3 -0
- package/templates/templates/ssr/ts/composables/useAuth.ts.hbs +56 -0
- package/templates/templates/ssr/ts/composables/useVasp.ts.hbs +10 -0
- package/templates/templates/ssr/ts/middleware/auth.ts.hbs +8 -0
- package/templates/templates/ssr/ts/nuxt.config.ts.hbs +19 -0
- package/templates/templates/ssr/ts/plugins/vasp.client.ts.hbs +17 -0
- package/templates/templates/ssr/ts/plugins/vasp.server.ts.hbs +33 -0
package/dist/vasp
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// @bun
|
|
3
|
-
#!/usr/bin/env bun
|
|
4
3
|
var y8=Object.create;var{getPrototypeOf:D8,defineProperty:P$,getOwnPropertyNames:x8}=Object;var L8=Object.prototype.hasOwnProperty;function s8(_){return this[_]}var B8,U8,Uq=(_,q,$)=>{var f=_!=null&&typeof _==="object";if(f){var K=q?B8??=new WeakMap:U8??=new WeakMap,P=K.get(_);if(P)return P}$=_!=null?y8(D8(_)):{};let j=q||!_||!_.__esModule?P$($,"default",{value:_,enumerable:!0}):$;for(let w of x8(_))if(!L8.call(j,w))P$(j,w,{get:s8.bind(_,w),enumerable:!0});if(f)K.set(_,j);return j};var H=(_,q)=>()=>(q||_((q={exports:{}}).exports,q),q.exports);var kq=import.meta.require;var Qq=H((A7,Fq)=>{var tq=process||{},j$=tq.argv||[],dq=tq.env||{},F8=!(!!dq.NO_COLOR||j$.includes("--no-color"))&&(!!dq.FORCE_COLOR||j$.includes("--color")||tq.platform==="win32"||(tq.stdout||{}).isTTY&&dq.TERM!=="dumb"||!!dq.CI),Q8=(_,q,$=_)=>(f)=>{let K=""+f,P=K.indexOf(q,_.length);return~P?_+qf(K,q,$,P)+q:_+K+q},qf=(_,q,$,f)=>{let K="",P=0;do K+=_.substring(P,f)+$,P=f+q.length,f=_.indexOf(q,P);while(~f);return K+_.substring(P)},v$=(_=F8)=>{let q=_?Q8:()=>String;return{isColorSupported:_,reset:q("\x1B[0m","\x1B[0m"),bold:q("\x1B[1m","\x1B[22m","\x1B[22m\x1B[1m"),dim:q("\x1B[2m","\x1B[22m","\x1B[22m\x1B[2m"),italic:q("\x1B[3m","\x1B[23m"),underline:q("\x1B[4m","\x1B[24m"),inverse:q("\x1B[7m","\x1B[27m"),hidden:q("\x1B[8m","\x1B[28m"),strikethrough:q("\x1B[9m","\x1B[29m"),black:q("\x1B[30m","\x1B[39m"),red:q("\x1B[31m","\x1B[39m"),green:q("\x1B[32m","\x1B[39m"),yellow:q("\x1B[33m","\x1B[39m"),blue:q("\x1B[34m","\x1B[39m"),magenta:q("\x1B[35m","\x1B[39m"),cyan:q("\x1B[36m","\x1B[39m"),white:q("\x1B[37m","\x1B[39m"),gray:q("\x1B[90m","\x1B[39m"),bgBlack:q("\x1B[40m","\x1B[49m"),bgRed:q("\x1B[41m","\x1B[49m"),bgGreen:q("\x1B[42m","\x1B[49m"),bgYellow:q("\x1B[43m","\x1B[49m"),bgBlue:q("\x1B[44m","\x1B[49m"),bgMagenta:q("\x1B[45m","\x1B[49m"),bgCyan:q("\x1B[46m","\x1B[49m"),bgWhite:q("\x1B[47m","\x1B[49m"),blackBright:q("\x1B[90m","\x1B[39m"),redBright:q("\x1B[91m","\x1B[39m"),greenBright:q("\x1B[92m","\x1B[39m"),yellowBright:q("\x1B[93m","\x1B[39m"),blueBright:q("\x1B[94m","\x1B[39m"),magentaBright:q("\x1B[95m","\x1B[39m"),cyanBright:q("\x1B[96m","\x1B[39m"),whiteBright:q("\x1B[97m","\x1B[39m"),bgBlackBright:q("\x1B[100m","\x1B[49m"),bgRedBright:q("\x1B[101m","\x1B[49m"),bgGreenBright:q("\x1B[102m","\x1B[49m"),bgYellowBright:q("\x1B[103m","\x1B[49m"),bgBlueBright:q("\x1B[104m","\x1B[49m"),bgMagentaBright:q("\x1B[105m","\x1B[49m"),bgCyanBright:q("\x1B[106m","\x1B[49m"),bgWhiteBright:q("\x1B[107m","\x1B[49m")}};Fq.exports=v$();Fq.exports.createColors=v$});var p=H((zf)=>{zf.__esModule=!0;zf.extend=z$;zf.indexOf=Of;zf.escapeExpression=Yf;zf.isEmpty=hf;zf.createFrame=Tf;zf.blockParams=kf;zf.appendContextPath=rf;var Pf={"&":"&","<":"<",">":">",'"':""","'":"'","`":"`","=":"="},jf=/[&<>"'`=]/g,vf=/[&<>"'`=]/;function wf(_){return Pf[_]}function z$(_){for(var q=1;q<arguments.length;q++)for(var $ in arguments[q])if(Object.prototype.hasOwnProperty.call(arguments[q],$))_[$]=arguments[q][$];return _}var j_=Object.prototype.toString;zf.toString=j_;var P_=function(q){return typeof q==="function"};if(P_(/x/))zf.isFunction=P_=function(_){return typeof _==="function"&&j_.call(_)==="[object Function]"};zf.isFunction=P_;var H$=Array.isArray||function(_){return _&&typeof _==="object"?j_.call(_)==="[object Array]":!1};zf.isArray=H$;function Of(_,q){for(var $=0,f=_.length;$<f;$++)if(_[$]===q)return $;return-1}function Yf(_){if(typeof _!=="string"){if(_&&_.toHTML)return _.toHTML();else if(_==null)return"";else if(!_)return _+"";_=""+_}if(!vf.test(_))return _;return _.replace(jf,wf)}function hf(_){if(!_&&_!==0)return!0;else if(H$(_)&&_.length===0)return!0;else return!1}function Tf(_){var q=z$({},_);return q._parent=_,q}function kf(_,q){return _.path=q,_}function rf(_,q){return(_?_+".":"")+q}});var t=H((W$,J$)=>{W$.__esModule=!0;var v_=["description","fileName","lineNumber","endLineNumber","message","name","number","stack"];function w_(_,q){var $=q&&q.loc,f=void 0,K=void 0,P=void 0,j=void 0;if($)f=$.start.line,K=$.end.line,P=$.start.column,j=$.end.column,_+=" - "+f+":"+P;var w=Error.prototype.constructor.call(this,_);for(var O=0;O<v_.length;O++)this[v_[O]]=w[v_[O]];if(Error.captureStackTrace)Error.captureStackTrace(this,w_);try{if($)if(this.lineNumber=f,this.endLineNumber=K,Object.defineProperty)Object.defineProperty(this,"column",{value:P,enumerable:!0}),Object.defineProperty(this,"endColumn",{value:j,enumerable:!0});else this.column=P,this.endColumn=j}catch(v){}}w_.prototype=Error();W$.default=w_;J$.exports=W$.default});var u$=H((Z$,m$)=>{Z$.__esModule=!0;var O_=p();Z$.default=function(_){_.registerHelper("blockHelperMissing",function(q,$){var{inverse:f,fn:K}=$;if(q===!0)return K(this);else if(q===!1||q==null)return f(this);else if(O_.isArray(q))if(q.length>0){if($.ids)$.ids=[$.name];return _.helpers.each(q,$)}else return f(this);else{if($.data&&$.ids){var P=O_.createFrame($.data);P.contextPath=O_.appendContextPath($.data.contextPath,$.name),$={data:P}}return K(q,$)}})};m$.exports=Z$.default});var A$=H((o$,n$)=>{o$.__esModule=!0;function af(_){return _&&_.__esModule?_:{default:_}}var zq=p(),bf=t(),Sf=af(bf);o$.default=function(_){_.registerHelper("each",function(q,$){if(!$)throw new Sf.default("Must pass iterator to #each");var{fn:f,inverse:K}=$,P=0,j="",w=void 0,O=void 0;if($.data&&$.ids)O=zq.appendContextPath($.data.contextPath,$.ids[0])+".";if(zq.isFunction(q))q=q.call(this);if($.data)w=zq.createFrame($.data);function v(W,m,a){if(w){if(w.key=W,w.index=m,w.first=m===0,w.last=!!a,O)w.contextPath=O+W}j=j+f(q[W],{data:w,blockParams:zq.blockParams([q[W],W],[O+W,null])})}if(q&&typeof q==="object")if(zq.isArray(q)){for(var h=q.length;P<h;P++)if(P in q)v(P,P,P===q.length-1)}else if(typeof Symbol==="function"&&q[Symbol.iterator]){var Y=[],T=q[Symbol.iterator]();for(var z=T.next();!z.done;z=T.next())Y.push(z.value);q=Y;for(var h=q.length;P<h;P++)v(P,P,P===q.length-1)}else(function(){var W=void 0;if(Object.keys(q).forEach(function(m){if(W!==void 0)v(W,P-1);W=m,P++}),W!==void 0)v(W,P-1,!0)})();if(P===0)j=K(this);return j})};n$.exports=o$.default});var e$=H((X$,p$)=>{X$.__esModule=!0;function Vf(_){return _&&_.__esModule?_:{default:_}}var Mf=t(),Nf=Vf(Mf);X$.default=function(_){_.registerHelper("helperMissing",function(){if(arguments.length===1)return;else throw new Nf.default('Missing helper: "'+arguments[arguments.length-1].name+'"')})};p$.exports=X$.default});var a$=H((G$,i$)=>{G$.__esModule=!0;function Rf(_){return _&&_.__esModule?_:{default:_}}var d$=p(),gf=t(),t$=Rf(gf);G$.default=function(_){_.registerHelper("if",function(q,$){if(arguments.length!=2)throw new t$.default("#if requires exactly one argument");if(d$.isFunction(q))q=q.call(this);if(!$.hash.includeZero&&!q||d$.isEmpty(q))return $.inverse(this);else return $.fn(this)}),_.registerHelper("unless",function(q,$){if(arguments.length!=2)throw new t$.default("#unless requires exactly one argument");return _.helpers.if.call(this,q,{fn:$.inverse,inverse:$.fn,hash:$.hash})})};i$.exports=G$.default});var C$=H((b$,S$)=>{b$.__esModule=!0;b$.default=function(_){_.registerHelper("log",function(){var q=[void 0],$=arguments[arguments.length-1];for(var f=0;f<arguments.length-1;f++)q.push(arguments[f]);var K=1;if($.hash.level!=null)K=$.hash.level;else if($.data&&$.data.level!=null)K=$.data.level;q[0]=K,_.log.apply(_,q)})};S$.exports=b$.default});var M$=H((l$,V$)=>{l$.__esModule=!0;l$.default=function(_){_.registerHelper("lookup",function(q,$,f){if(!q)return q;return f.lookupProperty(q,$)})};V$.exports=l$.default});var E$=H((N$,c$)=>{N$.__esModule=!0;function Bf(_){return _&&_.__esModule?_:{default:_}}var Hq=p(),Uf=t(),Ff=Bf(Uf);N$.default=function(_){_.registerHelper("with",function(q,$){if(arguments.length!=2)throw new Ff.default("#with requires exactly one argument");if(Hq.isFunction(q))q=q.call(this);var f=$.fn;if(!Hq.isEmpty(q)){var K=$.data;if($.data&&$.ids)K=Hq.createFrame($.data),K.contextPath=Hq.appendContextPath($.data.contextPath,$.ids[0]);return f(q,{data:K,blockParams:Hq.blockParams([q],[K&&K.contextPath])})}else return $.inverse(this)})};c$.exports=N$.default});var Y_=H((WK)=>{WK.__esModule=!0;WK.registerDefaultHelpers=zK;WK.moveHelperToHooks=HK;function L(_){return _&&_.__esModule?_:{default:_}}var _K=u$(),$K=L(_K),fK=A$(),KK=L(fK),PK=e$(),jK=L(PK),vK=a$(),wK=L(vK),OK=C$(),YK=L(OK),hK=M$(),TK=L(hK),kK=E$(),rK=L(kK);function zK(_){$K.default(_),KK.default(_),jK.default(_),wK.default(_),YK.default(_),TK.default(_),rK.default(_)}function HK(_,q,$){if(_.helpers[q]){if(_.hooks[q]=_.helpers[q],!$)delete _.helpers[q]}}});var I$=H((R$,g$)=>{R$.__esModule=!0;var uK=p();R$.default=function(_){_.registerDecorator("inline",function(q,$,f,K){var P=q;if(!$.partials)$.partials={},P=function(j,w){var O=f.partials;f.partials=uK.extend({},O,$.partials);var v=q(j,w);return f.partials=O,v};return $.partials[K.args[0]]=K.fn,P})};g$.exports=R$.default});var y$=H((dK)=>{dK.__esModule=!0;dK.registerDefaultDecorators=eK;function AK(_){return _&&_.__esModule?_:{default:_}}var XK=I$(),pK=AK(XK);function eK(_){pK.default(_)}});var h_=H((D$,x$)=>{D$.__esModule=!0;var iK=p(),fq={methodMap:["debug","info","warn","error"],level:"info",lookupLevel:function(q){if(typeof q==="string"){var $=iK.indexOf(fq.methodMap,q.toLowerCase());if($>=0)q=$;else q=parseInt(q,10)}return q},log:function(q){if(q=fq.lookupLevel(q),typeof console<"u"&&fq.lookupLevel(fq.level)<=q){var $=fq.methodMap[q];if(!console[$])$="log";for(var f=arguments.length,K=Array(f>1?f-1:0),P=1;P<f;P++)K[P-1]=arguments[P];console[$].apply(console,K)}}};D$.default=fq;x$.exports=D$.default});var L$=H((lK)=>{lK.__esModule=!0;lK.createNewLookupObject=CK;var SK=p();function CK(){for(var _=arguments.length,q=Array(_),$=0;$<_;$++)q[$]=arguments[$];return SK.extend.apply(void 0,[Object.create(null)].concat(q))}});var T_=H((DK)=>{DK.__esModule=!0;DK.createProtoAccessControl=RK;DK.resultIsAllowed=gK;DK.resetLoggedProperties=yK;function NK(_){return _&&_.__esModule?_:{default:_}}var s$=L$(),cK=h_(),EK=NK(cK),iq=Object.create(null);function RK(_){var q=Object.create(null);q.constructor=!1,q.__defineGetter__=!1,q.__defineSetter__=!1,q.__lookupGetter__=!1;var $=Object.create(null);return $.__proto__=!1,{properties:{whitelist:s$.createNewLookupObject($,_.allowedProtoProperties),defaultValue:_.allowProtoPropertiesByDefault},methods:{whitelist:s$.createNewLookupObject(q,_.allowedProtoMethods),defaultValue:_.allowProtoMethodsByDefault}}}function gK(_,q,$){if(typeof _==="function")return B$(q.methods,$);else return B$(q.properties,$)}function B$(_,q){if(_.whitelist[q]!==void 0)return _.whitelist[q]===!0;if(_.defaultValue!==void 0)return _.defaultValue;return IK(q),!1}function IK(_){if(iq[_]!==!0)iq[_]=!0,EK.default.log("error",'Handlebars: Access has been denied to resolve the property "'+_+`" because it is not an "own property" of its parent.
|
|
5
4
|
You can add a runtime option to disable the check or this warning:
|
|
6
5
|
See https://handlebarsjs.com/api-reference/runtime-options.html#options-to-control-prototype-access for details`)}function yK(){Object.keys(iq).forEach(function(_){delete iq[_]})}});var bq=H((vP)=>{vP.__esModule=!0;vP.HandlebarsEnvironment=z_;function U$(_){return _&&_.__esModule?_:{default:_}}var s=p(),UK=t(),k_=U$(UK),FK=Y_(),QK=y$(),qP=h_(),aq=U$(qP),_P=T_(),$P="4.7.8";vP.VERSION=$P;var fP=8;vP.COMPILER_REVISION=fP;var KP=7;vP.LAST_COMPATIBLE_COMPILER_REVISION=KP;var PP={1:"<= 1.0.rc.2",2:"== 1.0.0-rc.3",3:"== 1.0.0-rc.4",4:"== 1.x.x",5:"== 2.0.0-alpha.x",6:">= 2.0.0-beta.1",7:">= 4.0.0 <4.3.0",8:">= 4.3.0"};vP.REVISION_CHANGES=PP;var r_="[object Object]";function z_(_,q,$){this.helpers=_||{},this.partials=q||{},this.decorators=$||{},FK.registerDefaultHelpers(this),QK.registerDefaultDecorators(this)}z_.prototype={constructor:z_,logger:aq.default,log:aq.default.log,registerHelper:function(q,$){if(s.toString.call(q)===r_){if($)throw new k_.default("Arg not supported with multiple helpers");s.extend(this.helpers,q)}else this.helpers[q]=$},unregisterHelper:function(q){delete this.helpers[q]},registerPartial:function(q,$){if(s.toString.call(q)===r_)s.extend(this.partials,q);else{if(typeof $>"u")throw new k_.default('Attempting to register a partial called "'+q+'" as undefined');this.partials[q]=$}},unregisterPartial:function(q){delete this.partials[q]},registerDecorator:function(q,$){if(s.toString.call(q)===r_){if($)throw new k_.default("Arg not supported with multiple decorators");s.extend(this.decorators,q)}else this.decorators[q]=$},unregisterDecorator:function(q){delete this.decorators[q]},resetLoggedPropertyAccesses:function(){_P.resetLoggedProperties()}};var jP=aq.default.log;vP.log=jP;vP.createFrame=s.createFrame;vP.logger=aq.default});var q6=H((F$,Q$)=>{F$.__esModule=!0;function H_(_){this.string=_}H_.prototype.toString=H_.prototype.toHTML=function(){return""+this.string};F$.default=H_;Q$.exports=F$.default});var _6=H((mP)=>{mP.__esModule=!0;mP.wrapHelper=ZP;function ZP(_,q){if(typeof _!=="function")return _;var $=function(){var K=arguments[arguments.length-1];return arguments[arguments.length-1]=q(K),_.apply(this,arguments)};return $}});var j6=H((CP)=>{CP.__esModule=!0;CP.checkRevision=dP;CP.template=tP;CP.wrapProgram=Sq;CP.resolvePartial=GP;CP.invokePartial=iP;CP.noop=K6;function nP(_){return _&&_.__esModule?_:{default:_}}function AP(_){if(_&&_.__esModule)return _;else{var q={};if(_!=null){for(var $ in _)if(Object.prototype.hasOwnProperty.call(_,$))q[$]=_[$]}return q.default=_,q}}var XP=p(),M=AP(XP),pP=t(),N=nP(pP),c=bq(),$6=Y_(),eP=_6(),f6=T_();function dP(_){var q=_&&_[0]||1,$=c.COMPILER_REVISION;if(q>=c.LAST_COMPATIBLE_COMPILER_REVISION&&q<=c.COMPILER_REVISION)return;if(q<c.LAST_COMPATIBLE_COMPILER_REVISION){var f=c.REVISION_CHANGES[$],K=c.REVISION_CHANGES[q];throw new N.default("Template was precompiled with an older version of Handlebars than the current runtime. Please update your precompiler to a newer version ("+f+") or downgrade your runtime to an older version ("+K+").")}else throw new N.default("Template was precompiled with a newer version of Handlebars than the current runtime. Please update your runtime to a newer version ("+_[1]+").")}function tP(_,q){if(!q)throw new N.default("No environment passed to template");if(!_||!_.main)throw new N.default("Unknown template object: "+typeof _);_.main.decorator=_.main_d,q.VM.checkRevision(_.compiler);var $=_.compiler&&_.compiler[0]===7;function f(j,w,O){if(O.hash){if(w=M.extend({},w,O.hash),O.ids)O.ids[0]=!0}j=q.VM.resolvePartial.call(this,j,w,O);var v=M.extend({},O,{hooks:this.hooks,protoAccessControl:this.protoAccessControl}),h=q.VM.invokePartial.call(this,j,w,v);if(h==null&&q.compile)O.partials[O.name]=q.compile(j,_.compilerOptions,q),h=O.partials[O.name](w,v);if(h!=null){if(O.indent){var Y=h.split(`
|
package/package.json
CHANGED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
DATABASE_URL=postgres://user:password@localhost:5432/{{kebabCase appName}}
|
|
2
|
+
PORT={{backendPort}}
|
|
3
|
+
VITE_API_URL=http://localhost:{{backendPort}}/api
|
|
4
|
+
{{#if hasAuth}}
|
|
5
|
+
JWT_SECRET=change-me-in-production
|
|
6
|
+
{{#if (includes authMethods "google")}}
|
|
7
|
+
GOOGLE_CLIENT_ID=
|
|
8
|
+
GOOGLE_CLIENT_SECRET=
|
|
9
|
+
{{/if}}
|
|
10
|
+
{{#if (includes authMethods "github")}}
|
|
11
|
+
GITHUB_CLIENT_ID=
|
|
12
|
+
GITHUB_CLIENT_SECRET=
|
|
13
|
+
{{/if}}
|
|
14
|
+
{{/if}}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="auth-form">
|
|
3
|
+
<h2>Login</h2>
|
|
4
|
+
<form @submit.prevent="handleLogin">
|
|
5
|
+
<input v-model="username" type="text" placeholder="Username" required />
|
|
6
|
+
<input v-model="password" type="password" placeholder="Password" required />
|
|
7
|
+
<button type="submit" :disabled="loading">
|
|
8
|
+
\{{ loading ? 'Logging in...' : 'Login' }}
|
|
9
|
+
</button>
|
|
10
|
+
<p v-if="error" class="error">\{{ error }}</p>
|
|
11
|
+
</form>
|
|
12
|
+
<p>Don't have an account? <RouterLink to="/register">Register</RouterLink></p>
|
|
13
|
+
{{#if (includes authMethods "google")}}
|
|
14
|
+
<a href="/auth/google" class="oauth-btn">Continue with Google</a>
|
|
15
|
+
{{/if}}
|
|
16
|
+
{{#if (includes authMethods "github")}}
|
|
17
|
+
<a href="/auth/github" class="oauth-btn">Continue with GitHub</a>
|
|
18
|
+
{{/if}}
|
|
19
|
+
</div>
|
|
20
|
+
</template>
|
|
21
|
+
|
|
22
|
+
<script setup>
|
|
23
|
+
import { ref } from 'vue'
|
|
24
|
+
import { useRouter } from 'vue-router'
|
|
25
|
+
import { useAuth } from '../vasp/auth.js'
|
|
26
|
+
|
|
27
|
+
const router = useRouter()
|
|
28
|
+
const { login } = useAuth()
|
|
29
|
+
const username = ref('')
|
|
30
|
+
const password = ref('')
|
|
31
|
+
const loading = ref(false)
|
|
32
|
+
const error = ref('')
|
|
33
|
+
|
|
34
|
+
async function handleLogin() {
|
|
35
|
+
loading.value = true
|
|
36
|
+
error.value = ''
|
|
37
|
+
try {
|
|
38
|
+
await login(username.value, password.value)
|
|
39
|
+
router.push('/')
|
|
40
|
+
} catch (err) {
|
|
41
|
+
error.value = err?.data?.error || 'Login failed'
|
|
42
|
+
} finally {
|
|
43
|
+
loading.value = false
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
</script>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<template>
|
|
2
|
+
<div class="auth-form">
|
|
3
|
+
<h2>Create Account</h2>
|
|
4
|
+
<form @submit.prevent="handleRegister">
|
|
5
|
+
<input v-model="username" type="text" placeholder="Username" required />
|
|
6
|
+
<input v-model="email" type="email" placeholder="Email (optional)" />
|
|
7
|
+
<input v-model="password" type="password" placeholder="Password (min 8 chars)" required />
|
|
8
|
+
<button type="submit" :disabled="loading">
|
|
9
|
+
\{{ loading ? 'Creating account...' : 'Register' }}
|
|
10
|
+
</button>
|
|
11
|
+
<p v-if="error" class="error">\{{ error }}</p>
|
|
12
|
+
</form>
|
|
13
|
+
<p>Already have an account? <RouterLink to="/login">Login</RouterLink></p>
|
|
14
|
+
</div>
|
|
15
|
+
</template>
|
|
16
|
+
|
|
17
|
+
<script setup>
|
|
18
|
+
import { ref } from 'vue'
|
|
19
|
+
import { useRouter } from 'vue-router'
|
|
20
|
+
import { useAuth } from '../vasp/auth.js'
|
|
21
|
+
|
|
22
|
+
const router = useRouter()
|
|
23
|
+
const { register } = useAuth()
|
|
24
|
+
const username = ref('')
|
|
25
|
+
const email = ref('')
|
|
26
|
+
const password = ref('')
|
|
27
|
+
const loading = ref(false)
|
|
28
|
+
const error = ref('')
|
|
29
|
+
|
|
30
|
+
async function handleRegister() {
|
|
31
|
+
loading.value = true
|
|
32
|
+
error.value = ''
|
|
33
|
+
try {
|
|
34
|
+
await register(username.value, password.value, email.value || undefined)
|
|
35
|
+
router.push('/')
|
|
36
|
+
} catch (err) {
|
|
37
|
+
error.value = err?.data?.error || 'Registration failed'
|
|
38
|
+
} finally {
|
|
39
|
+
loading.value = false
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
</script>
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { Elysia } from 'elysia'
|
|
2
|
+
import { jwt } from '@elysiajs/jwt'
|
|
3
|
+
import { cookie } from '@elysiajs/cookie'
|
|
4
|
+
import { db } from '../db/client.{{ext}}'
|
|
5
|
+
import { users } from '../../drizzle/schema.{{ext}}'
|
|
6
|
+
import { eq } from 'drizzle-orm'
|
|
7
|
+
{{#if (includes authMethods "usernameAndPassword")}}
|
|
8
|
+
import { usernameAndPasswordRoutes } from './providers/usernameAndPassword.{{ext}}'
|
|
9
|
+
{{/if}}
|
|
10
|
+
{{#if (includes authMethods "google")}}
|
|
11
|
+
import { googleRoutes } from './providers/google.{{ext}}'
|
|
12
|
+
{{/if}}
|
|
13
|
+
{{#if (includes authMethods "github")}}
|
|
14
|
+
import { githubRoutes } from './providers/github.{{ext}}'
|
|
15
|
+
{{/if}}
|
|
16
|
+
|
|
17
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'change-me-in-production'
|
|
18
|
+
|
|
19
|
+
export const authPlugin = new Elysia({ name: 'auth-plugin' })
|
|
20
|
+
.use(jwt({ name: 'jwt', secret: JWT_SECRET }))
|
|
21
|
+
.use(cookie())
|
|
22
|
+
|
|
23
|
+
export const authRoutes = new Elysia({ prefix: '/auth' })
|
|
24
|
+
.use(authPlugin)
|
|
25
|
+
{{#if (includes authMethods "usernameAndPassword")}}
|
|
26
|
+
.use(usernameAndPasswordRoutes)
|
|
27
|
+
{{/if}}
|
|
28
|
+
{{#if (includes authMethods "google")}}
|
|
29
|
+
.use(googleRoutes)
|
|
30
|
+
{{/if}}
|
|
31
|
+
{{#if (includes authMethods "github")}}
|
|
32
|
+
.use(githubRoutes)
|
|
33
|
+
{{/if}}
|
|
34
|
+
.get('/me', async ({ jwt, cookie: { token }, set }) => {
|
|
35
|
+
const payload = await jwt.verify(token?.value ?? '')
|
|
36
|
+
if (!payload || typeof payload.userId !== 'number') {
|
|
37
|
+
set.status = 401
|
|
38
|
+
return { error: 'Unauthorized' }
|
|
39
|
+
}
|
|
40
|
+
const [user] = await db.select().from(users).where(eq(users.id, payload.userId)).limit(1)
|
|
41
|
+
if (!user) {
|
|
42
|
+
set.status = 401
|
|
43
|
+
return { error: 'User not found' }
|
|
44
|
+
}
|
|
45
|
+
const { passwordHash: _ph, ...safeUser } = user
|
|
46
|
+
return safeUser
|
|
47
|
+
})
|
|
48
|
+
.post('/logout', ({ cookie: { token }, set }) => {
|
|
49
|
+
token?.remove()
|
|
50
|
+
return { ok: true }
|
|
51
|
+
})
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Elysia } from 'elysia'
|
|
2
|
+
import { jwt } from '@elysiajs/jwt'
|
|
3
|
+
import { cookie } from '@elysiajs/cookie'
|
|
4
|
+
import { db } from '../db/client.{{ext}}'
|
|
5
|
+
import { users } from '../../drizzle/schema.{{ext}}'
|
|
6
|
+
import { eq } from 'drizzle-orm'
|
|
7
|
+
|
|
8
|
+
const JWT_SECRET = process.env.JWT_SECRET || 'change-me-in-production'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* requireAuth — Elysia plugin that verifies the JWT cookie and injects `user` into the context.
|
|
12
|
+
* Use on any route that requires authentication.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* new Elysia().use(requireAuth).get('/protected', ({ user }) => user)
|
|
16
|
+
*/
|
|
17
|
+
export const requireAuth = new Elysia({ name: 'require-auth' })
|
|
18
|
+
.use(jwt({ name: 'jwt', secret: JWT_SECRET }))
|
|
19
|
+
.use(cookie())
|
|
20
|
+
.derive(async ({ jwt, cookie: { token }, set }) => {
|
|
21
|
+
const payload = await jwt.verify(token?.value ?? '')
|
|
22
|
+
if (!payload || typeof payload.userId !== 'number') {
|
|
23
|
+
set.status = 401
|
|
24
|
+
throw new Error('Unauthorized')
|
|
25
|
+
}
|
|
26
|
+
const [user] = await db.select().from(users).where(eq(users.id, payload.userId)).limit(1)
|
|
27
|
+
if (!user) {
|
|
28
|
+
set.status = 401
|
|
29
|
+
throw new Error('User not found')
|
|
30
|
+
}
|
|
31
|
+
const { passwordHash: _ph, ...safeUser } = user
|
|
32
|
+
return { user: safeUser }
|
|
33
|
+
})
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { Elysia } from 'elysia'
|
|
2
|
+
import { db } from '../../db/client.{{ext}}'
|
|
3
|
+
import { users } from '../../../drizzle/schema.{{ext}}'
|
|
4
|
+
import { eq } from 'drizzle-orm'
|
|
5
|
+
import { authPlugin } from '../index.{{ext}}'
|
|
6
|
+
|
|
7
|
+
const GITHUB_CLIENT_ID = process.env.GITHUB_CLIENT_ID || ''
|
|
8
|
+
const GITHUB_CLIENT_SECRET = process.env.GITHUB_CLIENT_SECRET || ''
|
|
9
|
+
const REDIRECT_URI = process.env.GITHUB_REDIRECT_URI || 'http://localhost:{{backendPort}}/auth/github/callback'
|
|
10
|
+
|
|
11
|
+
export const githubRoutes = new Elysia()
|
|
12
|
+
.use(authPlugin)
|
|
13
|
+
.get('/github', ({ set }) => {
|
|
14
|
+
const params = new URLSearchParams({
|
|
15
|
+
client_id: GITHUB_CLIENT_ID,
|
|
16
|
+
redirect_uri: REDIRECT_URI,
|
|
17
|
+
scope: 'read:user user:email',
|
|
18
|
+
})
|
|
19
|
+
set.redirect = `https://github.com/login/oauth/authorize?${params}`
|
|
20
|
+
})
|
|
21
|
+
.get('/github/callback', async ({ query, jwt, cookie: { token }, set }) => {
|
|
22
|
+
const { code } = query
|
|
23
|
+
if (!code) { set.status = 400; return { error: 'Missing code' } }
|
|
24
|
+
|
|
25
|
+
const tokenRes = await fetch('https://github.com/login/oauth/access_token', {
|
|
26
|
+
method: 'POST',
|
|
27
|
+
headers: { 'Content-Type': 'application/json', Accept: 'application/json' },
|
|
28
|
+
body: JSON.stringify({ client_id: GITHUB_CLIENT_ID, client_secret: GITHUB_CLIENT_SECRET, code }),
|
|
29
|
+
})
|
|
30
|
+
const { access_token } = await tokenRes.json()
|
|
31
|
+
|
|
32
|
+
const userRes = await fetch('https://api.github.com/user', {
|
|
33
|
+
headers: { Authorization: `Bearer ${access_token}`, Accept: 'application/json' },
|
|
34
|
+
})
|
|
35
|
+
const ghUser = await userRes.json()
|
|
36
|
+
const githubId = String(ghUser.id)
|
|
37
|
+
const email = ghUser.email || `${ghUser.login}@github.local`
|
|
38
|
+
|
|
39
|
+
let [user] = await db.select().from(users).where(eq(users.githubId, githubId)).limit(1)
|
|
40
|
+
if (!user) {
|
|
41
|
+
;[user] = await db.insert(users).values({ username: ghUser.login, email, githubId }).returning()
|
|
42
|
+
}
|
|
43
|
+
if (!user) { set.status = 500; return { error: 'Failed to create user' } }
|
|
44
|
+
|
|
45
|
+
const tokenValue = await jwt.sign({ userId: user.id })
|
|
46
|
+
token.set({ value: tokenValue, httpOnly: true, sameSite: 'lax', path: '/' })
|
|
47
|
+
set.redirect = '/'
|
|
48
|
+
})
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { Elysia } from 'elysia'
|
|
2
|
+
import { db } from '../../db/client.{{ext}}'
|
|
3
|
+
import { users } from '../../../drizzle/schema.{{ext}}'
|
|
4
|
+
import { eq } from 'drizzle-orm'
|
|
5
|
+
import { authPlugin } from '../index.{{ext}}'
|
|
6
|
+
|
|
7
|
+
const GOOGLE_CLIENT_ID = process.env.GOOGLE_CLIENT_ID || ''
|
|
8
|
+
const GOOGLE_CLIENT_SECRET = process.env.GOOGLE_CLIENT_SECRET || ''
|
|
9
|
+
const REDIRECT_URI = process.env.GOOGLE_REDIRECT_URI || 'http://localhost:{{backendPort}}/auth/google/callback'
|
|
10
|
+
|
|
11
|
+
export const googleRoutes = new Elysia()
|
|
12
|
+
.use(authPlugin)
|
|
13
|
+
.get('/google', ({ set }) => {
|
|
14
|
+
const params = new URLSearchParams({
|
|
15
|
+
client_id: GOOGLE_CLIENT_ID,
|
|
16
|
+
redirect_uri: REDIRECT_URI,
|
|
17
|
+
response_type: 'code',
|
|
18
|
+
scope: 'openid email profile',
|
|
19
|
+
})
|
|
20
|
+
set.redirect = `https://accounts.google.com/o/oauth2/v2/auth?${params}`
|
|
21
|
+
})
|
|
22
|
+
.get('/google/callback', async ({ query, jwt, cookie: { token }, set }) => {
|
|
23
|
+
const { code } = query
|
|
24
|
+
if (!code) { set.status = 400; return { error: 'Missing code' } }
|
|
25
|
+
|
|
26
|
+
// Exchange code for tokens
|
|
27
|
+
const tokenRes = await fetch('https://oauth2.googleapis.com/token', {
|
|
28
|
+
method: 'POST',
|
|
29
|
+
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
30
|
+
body: new URLSearchParams({
|
|
31
|
+
code,
|
|
32
|
+
client_id: GOOGLE_CLIENT_ID,
|
|
33
|
+
client_secret: GOOGLE_CLIENT_SECRET,
|
|
34
|
+
redirect_uri: REDIRECT_URI,
|
|
35
|
+
grant_type: 'authorization_code',
|
|
36
|
+
}),
|
|
37
|
+
})
|
|
38
|
+
const tokenData = await tokenRes.json()
|
|
39
|
+
const idToken = tokenData.id_token
|
|
40
|
+
const payload = JSON.parse(Buffer.from(idToken.split('.')[1], 'base64').toString())
|
|
41
|
+
const googleId = payload.sub
|
|
42
|
+
const email = payload.email
|
|
43
|
+
|
|
44
|
+
let [user] = await db.select().from(users).where(eq(users.googleId, googleId)).limit(1)
|
|
45
|
+
if (!user) {
|
|
46
|
+
;[user] = await db.insert(users).values({ username: email, email, googleId }).returning()
|
|
47
|
+
}
|
|
48
|
+
if (!user) { set.status = 500; return { error: 'Failed to create user' } }
|
|
49
|
+
|
|
50
|
+
const tokenValue = await jwt.sign({ userId: user.id })
|
|
51
|
+
token.set({ value: tokenValue, httpOnly: true, sameSite: 'lax', path: '/' })
|
|
52
|
+
set.redirect = '/'
|
|
53
|
+
})
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { Elysia, t } from 'elysia'
|
|
2
|
+
import { db } from '../../db/client.{{ext}}'
|
|
3
|
+
import { users } from '../../../drizzle/schema.{{ext}}'
|
|
4
|
+
import { eq } from 'drizzle-orm'
|
|
5
|
+
import { authPlugin } from '../index.{{ext}}'
|
|
6
|
+
|
|
7
|
+
async function hashPassword(password{{#if isTypeScript}}: string{{/if}}) {
|
|
8
|
+
const encoder = new TextEncoder()
|
|
9
|
+
const data = encoder.encode(password)
|
|
10
|
+
const hash = await crypto.subtle.digest('SHA-256', data)
|
|
11
|
+
return Buffer.from(hash).toString('hex')
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async function verifyPassword(password{{#if isTypeScript}}: string{{/if}}, hash{{#if isTypeScript}}: string{{/if}}) {
|
|
15
|
+
return (await hashPassword(password)) === hash
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export const usernameAndPasswordRoutes = new Elysia()
|
|
19
|
+
.use(authPlugin)
|
|
20
|
+
.post(
|
|
21
|
+
'/register',
|
|
22
|
+
async ({ body, jwt, cookie: { token }, set }) => {
|
|
23
|
+
const existing = await db.select().from(users).where(eq(users.username, body.username)).limit(1)
|
|
24
|
+
if (existing.length > 0) {
|
|
25
|
+
set.status = 400
|
|
26
|
+
return { error: 'Username already taken' }
|
|
27
|
+
}
|
|
28
|
+
const passwordHash = await hashPassword(body.password)
|
|
29
|
+
const [user] = await db
|
|
30
|
+
.insert(users)
|
|
31
|
+
.values({ username: body.username, email: body.email ?? null, passwordHash })
|
|
32
|
+
.returning()
|
|
33
|
+
if (!user) {
|
|
34
|
+
set.status = 500
|
|
35
|
+
return { error: 'Failed to create user' }
|
|
36
|
+
}
|
|
37
|
+
const tokenValue = await jwt.sign({ userId: user.id })
|
|
38
|
+
token.set({ value: tokenValue, httpOnly: true, sameSite: 'lax', path: '/' })
|
|
39
|
+
const { passwordHash: _ph, ...safeUser } = user
|
|
40
|
+
return safeUser
|
|
41
|
+
},
|
|
42
|
+
{
|
|
43
|
+
body: t.Object({
|
|
44
|
+
username: t.String({ minLength: 3 }),
|
|
45
|
+
password: t.String({ minLength: 8 }),
|
|
46
|
+
email: t.Optional(t.String({ format: 'email' })),
|
|
47
|
+
}),
|
|
48
|
+
},
|
|
49
|
+
)
|
|
50
|
+
.post(
|
|
51
|
+
'/login',
|
|
52
|
+
async ({ body, jwt, cookie: { token }, set }) => {
|
|
53
|
+
const [user] = await db.select().from(users).where(eq(users.username, body.username)).limit(1)
|
|
54
|
+
if (!user || !user.passwordHash || !(await verifyPassword(body.password, user.passwordHash))) {
|
|
55
|
+
set.status = 401
|
|
56
|
+
return { error: 'Invalid username or password' }
|
|
57
|
+
}
|
|
58
|
+
const tokenValue = await jwt.sign({ userId: user.id })
|
|
59
|
+
token.set({ value: tokenValue, httpOnly: true, sameSite: 'lax', path: '/' })
|
|
60
|
+
const { passwordHash: _ph, ...safeUser } = user
|
|
61
|
+
return safeUser
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
body: t.Object({
|
|
65
|
+
username: t.String(),
|
|
66
|
+
password: t.String(),
|
|
67
|
+
}),
|
|
68
|
+
},
|
|
69
|
+
)
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { pgTable, serial, text, timestamp, boolean } from 'drizzle-orm/pg-core'
|
|
2
|
+
{{#if isTypeScript}}
|
|
3
|
+
import type { InferSelectModel, InferInsertModel } from 'drizzle-orm'
|
|
4
|
+
{{/if}}
|
|
5
|
+
|
|
6
|
+
{{#if hasAuth}}
|
|
7
|
+
// Users table — generated by Vasp auth system
|
|
8
|
+
export const users = pgTable('users', {
|
|
9
|
+
id: serial('id').primaryKey(),
|
|
10
|
+
username: text('username').notNull().unique(),
|
|
11
|
+
email: text('email').unique(),
|
|
12
|
+
passwordHash: text('password_hash'),
|
|
13
|
+
googleId: text('google_id').unique(),
|
|
14
|
+
githubId: text('github_id').unique(),
|
|
15
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
16
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
17
|
+
})
|
|
18
|
+
{{#if isTypeScript}}
|
|
19
|
+
export type User = InferSelectModel<typeof users>
|
|
20
|
+
export type NewUser = InferInsertModel<typeof users>
|
|
21
|
+
{{/if}}
|
|
22
|
+
|
|
23
|
+
{{/if}}
|
|
24
|
+
{{#each cruds}}
|
|
25
|
+
// {{entity}} table — add your columns below
|
|
26
|
+
export const {{camelCase entity}}s = pgTable('{{camelCase entity}}s', {
|
|
27
|
+
id: serial('id').primaryKey(),
|
|
28
|
+
// TODO: Add your {{entity}} columns here
|
|
29
|
+
createdAt: timestamp('created_at').defaultNow().notNull(),
|
|
30
|
+
updatedAt: timestamp('updated_at').defaultNow().notNull(),
|
|
31
|
+
})
|
|
32
|
+
{{#if ../isTypeScript}}
|
|
33
|
+
export type {{pascalCase entity}} = InferSelectModel<typeof {{camelCase entity}}s>
|
|
34
|
+
export type New{{pascalCase entity}} = InferInsertModel<typeof {{camelCase entity}}s>
|
|
35
|
+
{{/if}}
|
|
36
|
+
|
|
37
|
+
{{/each}}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { getBoss } from './boss.{{ext}}'
|
|
2
|
+
import { {{namedExport}} } from '{{importPath fnSource ext}}'
|
|
3
|
+
|
|
4
|
+
const JOB_NAME = '{{camelCase name}}'
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Register the '{{name}}' job worker with PgBoss.
|
|
8
|
+
* Called once on server startup.
|
|
9
|
+
*/
|
|
10
|
+
export async function register{{pascalCase name}}Worker() {
|
|
11
|
+
const boss = await getBoss()
|
|
12
|
+
await boss.work(JOB_NAME, async (job) => {
|
|
13
|
+
await {{namedExport}}(job.data)
|
|
14
|
+
})
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Schedule a '{{name}}' job.
|
|
19
|
+
* @param {unknown} data - Data to pass to the job handler
|
|
20
|
+
*/
|
|
21
|
+
export async function schedule{{pascalCase name}}(data) {
|
|
22
|
+
const boss = await getBoss()
|
|
23
|
+
return boss.send(JOB_NAME, data)
|
|
24
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import PgBoss from 'pg-boss'
|
|
2
|
+
|
|
3
|
+
const connectionString = process.env.DATABASE_URL
|
|
4
|
+
if (!connectionString) throw new Error('DATABASE_URL is required for PgBoss job queue')
|
|
5
|
+
|
|
6
|
+
// Singleton PgBoss instance shared across all job workers
|
|
7
|
+
let boss = null
|
|
8
|
+
|
|
9
|
+
export async function getBoss() {
|
|
10
|
+
if (!boss) {
|
|
11
|
+
boss = new PgBoss(connectionString)
|
|
12
|
+
await boss.start()
|
|
13
|
+
}
|
|
14
|
+
return boss
|
|
15
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{kebabCase appName}}",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"dev": "vasp start",
|
|
8
|
+
"build": "vasp build",
|
|
9
|
+
"dev:server": "bun --hot server/index.{{ext}}",
|
|
10
|
+
"dev:client": "{{#if isSpa}}vite{{else}}nuxt dev{{/if}}"
|
|
11
|
+
},
|
|
12
|
+
"dependencies": {
|
|
13
|
+
"@vasp-framework/runtime": "^0.1.0",
|
|
14
|
+
"elysia": "^1.1.0",
|
|
15
|
+
"@elysiajs/cors": "^1.1.0",
|
|
16
|
+
"@elysiajs/static": "^1.1.0",
|
|
17
|
+
"drizzle-orm": "^0.36.0",
|
|
18
|
+
"postgres": "^3.4.0",
|
|
19
|
+
"ofetch": "^1.3.4",
|
|
20
|
+
"vue": "^3.5.0"{{#if isSpa}},
|
|
21
|
+
"vue-router": "^4.4.0"{{else}},
|
|
22
|
+
"nuxt": "^4.0.0"{{/if}}{{#if hasJobs}},
|
|
23
|
+
"pg-boss": "^10.0.0"{{/if}}
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"drizzle-kit": "^0.28.0"{{#if isSpa}},
|
|
27
|
+
"@vitejs/plugin-vue": "^5.2.0",
|
|
28
|
+
"vite": "^6.0.0"{{/if}}{{#if isTypeScript}},
|
|
29
|
+
"typescript": "^5.6.0",
|
|
30
|
+
"vue-tsc": "^2.0.0"{{/if}}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { drizzle } from 'drizzle-orm/postgres-js'
|
|
2
|
+
import postgres from 'postgres'
|
|
3
|
+
import * as schema from '../../drizzle/schema.{{ext}}'
|
|
4
|
+
|
|
5
|
+
const connectionString = process.env.DATABASE_URL
|
|
6
|
+
|
|
7
|
+
if (!connectionString) {
|
|
8
|
+
throw new Error('DATABASE_URL environment variable is required')
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
const client = postgres(connectionString)
|
|
12
|
+
export const db = drizzle(client, { schema })
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Elysia } from 'elysia'
|
|
2
|
+
import { cors } from '@elysiajs/cors'
|
|
3
|
+
import { staticPlugin } from '@elysiajs/static'
|
|
4
|
+
import { db } from './db/client.{{ext}}'
|
|
5
|
+
{{#if hasAuth}}
|
|
6
|
+
import { authRoutes } from './auth/index.{{ext}}'
|
|
7
|
+
{{/if}}
|
|
8
|
+
{{#each queries}}
|
|
9
|
+
import { {{camelCase name}}Route } from './routes/queries/{{camelCase name}}.{{../ext}}'
|
|
10
|
+
{{/each}}
|
|
11
|
+
{{#each actions}}
|
|
12
|
+
import { {{camelCase name}}Route } from './routes/actions/{{camelCase name}}.{{../ext}}'
|
|
13
|
+
{{/each}}
|
|
14
|
+
{{#each cruds}}
|
|
15
|
+
import { {{camelCase entity}}CrudRoutes } from './routes/crud/{{camelCase entity}}.{{../ext}}'
|
|
16
|
+
{{/each}}
|
|
17
|
+
{{#if hasRealtime}}
|
|
18
|
+
import { realtimeRoutes } from './routes/realtime/index.{{ext}}'
|
|
19
|
+
{{/if}}
|
|
20
|
+
{{#each jobs}}
|
|
21
|
+
import { {{camelCase name}}ScheduleRoute } from './routes/jobs/{{camelCase name}}Schedule.{{../ext}}'
|
|
22
|
+
{{/each}}
|
|
23
|
+
|
|
24
|
+
const PORT = Number(process.env.PORT) || {{backendPort}}
|
|
25
|
+
|
|
26
|
+
const app = new Elysia()
|
|
27
|
+
.use(cors({
|
|
28
|
+
origin: process.env.CORS_ORIGIN || 'http://localhost:{{frontendPort}}',
|
|
29
|
+
credentials: true,
|
|
30
|
+
}))
|
|
31
|
+
.get('/api/health', () => ({ status: 'ok', version: '{{vaspVersion}}' }))
|
|
32
|
+
{{#if hasAuth}}
|
|
33
|
+
.use(authRoutes)
|
|
34
|
+
{{/if}}
|
|
35
|
+
{{#each queries}}
|
|
36
|
+
.use({{camelCase name}}Route)
|
|
37
|
+
{{/each}}
|
|
38
|
+
{{#each actions}}
|
|
39
|
+
.use({{camelCase name}}Route)
|
|
40
|
+
{{/each}}
|
|
41
|
+
{{#each cruds}}
|
|
42
|
+
.use({{camelCase entity}}CrudRoutes)
|
|
43
|
+
{{/each}}
|
|
44
|
+
{{#if hasRealtime}}
|
|
45
|
+
.use(realtimeRoutes)
|
|
46
|
+
{{/if}}
|
|
47
|
+
{{#each jobs}}
|
|
48
|
+
.use({{camelCase name}}ScheduleRoute)
|
|
49
|
+
{{/each}}
|
|
50
|
+
.listen(PORT)
|
|
51
|
+
|
|
52
|
+
console.log(`🚀 Vasp backend running at http://localhost:${PORT}`)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { Elysia } from 'elysia'
|
|
2
|
+
import { db } from '../../db/client.{{ext}}'
|
|
3
|
+
{{#if requiresAuth}}
|
|
4
|
+
import { requireAuth } from '../../auth/middleware.{{ext}}'
|
|
5
|
+
{{/if}}
|
|
6
|
+
import { {{namedExport}} } from '{{importPath fnSource ext}}'
|
|
7
|
+
|
|
8
|
+
export const {{camelCase name}}Route = new Elysia()
|
|
9
|
+
{{#if requiresAuth}}
|
|
10
|
+
.use(requireAuth)
|
|
11
|
+
.post('/api/actions/{{camelCase name}}', async ({ body, user }) => {
|
|
12
|
+
const result = await {{namedExport}}({ db, user, args: body })
|
|
13
|
+
return result
|
|
14
|
+
})
|
|
15
|
+
{{else}}
|
|
16
|
+
.post('/api/actions/{{camelCase name}}', async ({ body }) => {
|
|
17
|
+
const result = await {{namedExport}}({ db, args: body })
|
|
18
|
+
return result
|
|
19
|
+
})
|
|
20
|
+
{{/if}}
|