Add api-server and CLI applications
- Create api-server with Bun.serve: - oRPC router with stub handlers for all procedures - Auth middleware placeholder - CORS configuration - Create CLI tool with stricli: - bootstrap command for initial superuser creation - Placeholder commands for auth, user, org management Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
22
apps/api-server/package.json
Normal file
22
apps/api-server/package.json
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"name": "api-server",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "bun run --hot src/index.ts",
|
||||||
|
"build": "bun build src/index.ts --outdir dist",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@orpc/server": "^1.13.2",
|
||||||
|
"@reviq/api-contract": "workspace:*",
|
||||||
|
"@reviq/db": "workspace:*"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@macalinao/tsconfig": "catalog:",
|
||||||
|
"typescript": "catalog:"
|
||||||
|
}
|
||||||
|
}
|
||||||
22
apps/api-server/src/index.ts
Normal file
22
apps/api-server/src/index.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import { RPCHandler } from "@orpc/server/fetch";
|
||||||
|
import { router } from "./router.js";
|
||||||
|
|
||||||
|
const handler = new RPCHandler(router);
|
||||||
|
|
||||||
|
Bun.serve({
|
||||||
|
port: process.env.PORT || 3001,
|
||||||
|
async fetch(request) {
|
||||||
|
const url = new URL(request.url);
|
||||||
|
|
||||||
|
if (url.pathname.startsWith("/api/v1/rpc")) {
|
||||||
|
const { response } = await handler.handle(request, {
|
||||||
|
prefix: "/api/v1/rpc",
|
||||||
|
});
|
||||||
|
return response ?? new Response("Not Found", { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
return new Response("Not Found", { status: 404 });
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log("API server running on port", process.env.PORT || 3001);
|
||||||
14
apps/api-server/src/middleware/auth.ts
Normal file
14
apps/api-server/src/middleware/auth.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Authentication middleware for oRPC server
|
||||||
|
*
|
||||||
|
* This middleware will be used to:
|
||||||
|
* - Verify JWT tokens from Authorization header
|
||||||
|
* - Extract user context from valid tokens
|
||||||
|
* - Attach user information to request context
|
||||||
|
*
|
||||||
|
* TODO: Implement in Phase 2
|
||||||
|
*/
|
||||||
|
|
||||||
|
export const authMiddleware = async (): Promise<never> => {
|
||||||
|
throw new Error("Auth middleware not implemented");
|
||||||
|
};
|
||||||
0
apps/api-server/src/procedures/.gitkeep
Normal file
0
apps/api-server/src/procedures/.gitkeep
Normal file
347
apps/api-server/src/router.ts
Normal file
347
apps/api-server/src/router.ts
Normal file
@@ -0,0 +1,347 @@
|
|||||||
|
import { implement } from "@orpc/server";
|
||||||
|
import { contract } from "@reviq/api-contract";
|
||||||
|
|
||||||
|
const os = implement(contract);
|
||||||
|
|
||||||
|
// Auth procedures
|
||||||
|
const signup = os.auth.signup.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const verifyEmail = os.auth.verifyEmail.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const resendVerificationEmail = os.auth.resendVerificationEmail.handler(
|
||||||
|
async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const createLoginRequest = os.auth.createLoginRequest.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const loginPassword = os.auth.loginPassword.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const loginPasswordConfirm = os.auth.loginPasswordConfirm.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const loginIfRequestIsCompleted = os.auth.loginIfRequestIsCompleted.handler(
|
||||||
|
async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const forgotPassword = os.auth.forgotPassword.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const resetPassword = os.auth.resetPassword.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const logout = os.auth.logout.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
// WebAuthn procedures
|
||||||
|
const createRegistrationOptions =
|
||||||
|
os.auth.webauthn.createRegistrationOptions.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const verifyRegistration = os.auth.webauthn.verifyRegistration.handler(
|
||||||
|
async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
const createAuthenticationOptions =
|
||||||
|
os.auth.webauthn.createAuthenticationOptions.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const verifyAuthentication = os.auth.webauthn.verifyAuthentication.handler(
|
||||||
|
async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Me procedures
|
||||||
|
const meGet = os.me.get.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const setupProfile = os.me.setupProfile.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const updateProfile = os.me.updateProfile.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const meDelete = os.me.delete.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const setPassword = os.me.setPassword.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const listPasskeys = os.me.listPasskeys.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const createPasskey = os.me.createPasskey.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const renamePasskey = os.me.renamePasskey.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const deletePasskey = os.me.deletePasskey.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const listSessions = os.me.listSessions.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const revokeSession = os.me.revokeSession.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const revokeAllSessions = os.me.revokeAllSessions.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const getDeviceInfo = os.me.getDeviceInfo.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const trustDevice = os.me.trustDevice.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const listTrustedDevices = os.me.listTrustedDevices.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const untrustDevice = os.me.untrustDevice.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const revokeAllTrustedDevices = os.me.revokeAllTrustedDevices.handler(
|
||||||
|
async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Orgs procedures
|
||||||
|
const orgsList = os.orgs.list.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgsCreate = os.orgs.create.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgsGet = os.orgs.get.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgsUpdate = os.orgs.update.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgsDelete = os.orgs.delete.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgsLeave = os.orgs.leave.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Orgs members procedures
|
||||||
|
const membersList = os.orgs.members.list.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const membersUpdateRole = os.orgs.members.updateRole.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const membersRemove = os.orgs.members.remove.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Orgs invites procedures
|
||||||
|
const invitesList = os.orgs.invites.list.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const invitesCreate = os.orgs.invites.create.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const invitesCancel = os.orgs.invites.cancel.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const invitesAccept = os.orgs.invites.accept.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Orgs sites procedures
|
||||||
|
const sitesList = os.orgs.sites.list.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Admin orgs procedures
|
||||||
|
const adminOrgsList = os.admin.orgs.list.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminOrgsGet = os.admin.orgs.get.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminOrgsCreate = os.admin.orgs.create.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminOrgsUpdate = os.admin.orgs.update.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminOrgsDelete = os.admin.orgs.delete.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminOrgsListSites = os.admin.orgs.listSites.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminOrgsAddSite = os.admin.orgs.addSite.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminOrgsRemoveSite = os.admin.orgs.removeSite.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Admin users procedures
|
||||||
|
const adminUsersList = os.admin.users.list.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminUsersGet = os.admin.users.get.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminUsersCreate = os.admin.users.create.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminUsersUpdate = os.admin.users.update.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
const adminUsersConfirmEmail = os.admin.users.confirmEmail.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Admin auth procedures
|
||||||
|
const adminAuthCompleteLogin = os.admin.auth.completeLogin.handler(async () => {
|
||||||
|
throw new Error("Not implemented");
|
||||||
|
});
|
||||||
|
|
||||||
|
// Build the router
|
||||||
|
export const router = os.router({
|
||||||
|
auth: {
|
||||||
|
signup,
|
||||||
|
verifyEmail,
|
||||||
|
resendVerificationEmail,
|
||||||
|
createLoginRequest,
|
||||||
|
loginPassword,
|
||||||
|
loginPasswordConfirm,
|
||||||
|
loginIfRequestIsCompleted,
|
||||||
|
forgotPassword,
|
||||||
|
resetPassword,
|
||||||
|
logout,
|
||||||
|
webauthn: {
|
||||||
|
createRegistrationOptions,
|
||||||
|
verifyRegistration,
|
||||||
|
createAuthenticationOptions,
|
||||||
|
verifyAuthentication,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
me: {
|
||||||
|
get: meGet,
|
||||||
|
setupProfile,
|
||||||
|
updateProfile,
|
||||||
|
delete: meDelete,
|
||||||
|
setPassword,
|
||||||
|
listPasskeys,
|
||||||
|
createPasskey,
|
||||||
|
renamePasskey,
|
||||||
|
deletePasskey,
|
||||||
|
listSessions,
|
||||||
|
revokeSession,
|
||||||
|
revokeAllSessions,
|
||||||
|
getDeviceInfo,
|
||||||
|
trustDevice,
|
||||||
|
listTrustedDevices,
|
||||||
|
untrustDevice,
|
||||||
|
revokeAllTrustedDevices,
|
||||||
|
},
|
||||||
|
orgs: {
|
||||||
|
list: orgsList,
|
||||||
|
create: orgsCreate,
|
||||||
|
get: orgsGet,
|
||||||
|
update: orgsUpdate,
|
||||||
|
delete: orgsDelete,
|
||||||
|
leave: orgsLeave,
|
||||||
|
members: {
|
||||||
|
list: membersList,
|
||||||
|
updateRole: membersUpdateRole,
|
||||||
|
remove: membersRemove,
|
||||||
|
},
|
||||||
|
invites: {
|
||||||
|
list: invitesList,
|
||||||
|
create: invitesCreate,
|
||||||
|
cancel: invitesCancel,
|
||||||
|
accept: invitesAccept,
|
||||||
|
},
|
||||||
|
sites: {
|
||||||
|
list: sitesList,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
admin: {
|
||||||
|
orgs: {
|
||||||
|
list: adminOrgsList,
|
||||||
|
get: adminOrgsGet,
|
||||||
|
create: adminOrgsCreate,
|
||||||
|
update: adminOrgsUpdate,
|
||||||
|
delete: adminOrgsDelete,
|
||||||
|
listSites: adminOrgsListSites,
|
||||||
|
addSite: adminOrgsAddSite,
|
||||||
|
removeSite: adminOrgsRemoveSite,
|
||||||
|
},
|
||||||
|
users: {
|
||||||
|
list: adminUsersList,
|
||||||
|
get: adminUsersGet,
|
||||||
|
create: adminUsersCreate,
|
||||||
|
update: adminUsersUpdate,
|
||||||
|
confirmEmail: adminUsersConfirmEmail,
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
completeLogin: adminAuthCompleteLogin,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
24
apps/api-server/tsconfig.json
Normal file
24
apps/api-server/tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"extends": "@macalinao/tsconfig/tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"types": ["@types/bun"],
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"isolatedDeclarations": false,
|
||||||
|
"composite": false
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
25
apps/cli/package.json
Normal file
25
apps/cli/package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "@reviq/cli",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"private": true,
|
||||||
|
"type": "module",
|
||||||
|
"bin": {
|
||||||
|
"reviq": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "bun build src/bin/reviq.ts --outdir dist --target bun",
|
||||||
|
"cli": "bun run src/bin/reviq.ts",
|
||||||
|
"typecheck": "tsc --noEmit",
|
||||||
|
"clean": "rm -rf dist"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@stricli/core": "^1.2.5",
|
||||||
|
"@reviq/db": "workspace:*",
|
||||||
|
"@noble/hashes": "^2.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/bun": "latest",
|
||||||
|
"@macalinao/tsconfig": "catalog:",
|
||||||
|
"typescript": "catalog:"
|
||||||
|
}
|
||||||
|
}
|
||||||
122
apps/cli/src/bin/reviq.ts
Normal file
122
apps/cli/src/bin/reviq.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
#!/usr/bin/env bun
|
||||||
|
|
||||||
|
import {
|
||||||
|
buildApplication,
|
||||||
|
buildCommand,
|
||||||
|
buildRouteMap,
|
||||||
|
run,
|
||||||
|
} from "@stricli/core";
|
||||||
|
|
||||||
|
// Lazy load command implementations
|
||||||
|
const bootstrap = buildCommand({
|
||||||
|
loader: async () => import("../commands/bootstrap.js"),
|
||||||
|
parameters: {},
|
||||||
|
docs: {
|
||||||
|
brief: "Create a superuser account",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const authLogin = buildCommand({
|
||||||
|
loader: async () => import("../commands/auth.js").then((m) => m.login),
|
||||||
|
parameters: {},
|
||||||
|
docs: { brief: "Login to RevIQ (stub)" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const authLogout = buildCommand({
|
||||||
|
loader: async () => import("../commands/auth.js").then((m) => m.logout),
|
||||||
|
parameters: {},
|
||||||
|
docs: { brief: "Logout from RevIQ (stub)" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const authStatus = buildCommand({
|
||||||
|
loader: async () => import("../commands/auth.js").then((m) => m.status),
|
||||||
|
parameters: {},
|
||||||
|
docs: { brief: "Check authentication status (stub)" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const authCommand = buildRouteMap({
|
||||||
|
routes: {
|
||||||
|
login: authLogin,
|
||||||
|
logout: authLogout,
|
||||||
|
status: authStatus,
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
brief: "Authentication commands",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const userCreate = buildCommand({
|
||||||
|
loader: async () => import("../commands/user.js").then((m) => m.create),
|
||||||
|
parameters: {},
|
||||||
|
docs: { brief: "Create a new user (stub)" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const userConfirmEmail = buildCommand({
|
||||||
|
loader: async () => import("../commands/user.js").then((m) => m.confirmEmail),
|
||||||
|
parameters: {},
|
||||||
|
docs: { brief: "Confirm user email (stub)" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const userCommand = buildRouteMap({
|
||||||
|
routes: {
|
||||||
|
create: userCreate,
|
||||||
|
"confirm-email": userConfirmEmail,
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
brief: "User management commands",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgCreate = buildCommand({
|
||||||
|
loader: async () => import("../commands/org.js").then((m) => m.create),
|
||||||
|
parameters: {},
|
||||||
|
docs: { brief: "Create an organization (stub)" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgList = buildCommand({
|
||||||
|
loader: async () => import("../commands/org.js").then((m) => m.list),
|
||||||
|
parameters: {},
|
||||||
|
docs: { brief: "List organizations (stub)" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgAddSite = buildCommand({
|
||||||
|
loader: async () => import("../commands/org.js").then((m) => m.addSite),
|
||||||
|
parameters: {},
|
||||||
|
docs: { brief: "Add a site to an organization (stub)" },
|
||||||
|
});
|
||||||
|
|
||||||
|
const orgCommand = buildRouteMap({
|
||||||
|
routes: {
|
||||||
|
create: orgCreate,
|
||||||
|
list: orgList,
|
||||||
|
"add-site": orgAddSite,
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
brief: "Organization management commands",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const rootMap = buildRouteMap({
|
||||||
|
routes: {
|
||||||
|
bootstrap,
|
||||||
|
auth: authCommand,
|
||||||
|
user: userCommand,
|
||||||
|
org: orgCommand,
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
brief: "RevIQ CLI for database and user management",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const app = buildApplication(rootMap, {
|
||||||
|
name: "reviq",
|
||||||
|
versionInfo: {
|
||||||
|
currentVersion: "0.0.0",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
process,
|
||||||
|
};
|
||||||
|
|
||||||
|
await run(app, process.argv.slice(2), context);
|
||||||
25
apps/cli/src/commands/auth.ts
Normal file
25
apps/cli/src/commands/auth.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { CommandContext } from "@stricli/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Login command stub
|
||||||
|
*/
|
||||||
|
export async function login(this: CommandContext): Promise<void> {
|
||||||
|
console.log("Auth login command - Not implemented");
|
||||||
|
console.log("This command will authenticate a user and store credentials");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Logout command stub
|
||||||
|
*/
|
||||||
|
export async function logout(this: CommandContext): Promise<void> {
|
||||||
|
console.log("Auth logout command - Not implemented");
|
||||||
|
console.log("This command will clear stored authentication credentials");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Status command stub
|
||||||
|
*/
|
||||||
|
export async function status(this: CommandContext): Promise<void> {
|
||||||
|
console.log("Auth status command - Not implemented");
|
||||||
|
console.log("This command will show current authentication status");
|
||||||
|
}
|
||||||
77
apps/cli/src/commands/bootstrap.ts
Normal file
77
apps/cli/src/commands/bootstrap.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import type { CommandContext } from "@stricli/core";
|
||||||
|
// Password hashing imports (for future implementation)
|
||||||
|
// import { scrypt } from "@noble/hashes/scrypt";
|
||||||
|
// import { bytesToHex, utf8ToBytes } from "@noble/hashes/utils";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bootstrap command - creates a superuser account
|
||||||
|
*
|
||||||
|
* This command should be run after dbmate migration to set up
|
||||||
|
* the initial superuser account.
|
||||||
|
*
|
||||||
|
* Uses scrypt for password hashing (Cloudflare Workers compatible via @noble/hashes)
|
||||||
|
*/
|
||||||
|
export default async function (this: CommandContext): Promise<void> {
|
||||||
|
console.log("RevIQ Bootstrap - Create Superuser");
|
||||||
|
console.log("===================================\n");
|
||||||
|
|
||||||
|
// In a real implementation, we would:
|
||||||
|
// 1. Prompt for email and password using readline or prompts
|
||||||
|
// 2. Validate the input
|
||||||
|
// 3. Hash the password with scrypt (via @noble/hashes)
|
||||||
|
// 4. Connect to the database using @reviq/db
|
||||||
|
// 5. Insert the user with is_superuser=true
|
||||||
|
// 6. Handle errors appropriately
|
||||||
|
|
||||||
|
console.log("TODO: Implement bootstrap command");
|
||||||
|
console.log("\nThis command will:");
|
||||||
|
console.log(" 1. Prompt for email address");
|
||||||
|
console.log(" 2. Prompt for password (with confirmation)");
|
||||||
|
console.log(" 3. Hash password using scrypt (@noble/hashes)");
|
||||||
|
console.log(" 4. Create user in database with is_superuser=true");
|
||||||
|
console.log("\nRequirements:");
|
||||||
|
console.log(" - Database must be migrated (run 'dbmate up' first)");
|
||||||
|
console.log(" - DATABASE_URL environment variable must be set");
|
||||||
|
|
||||||
|
// Example of what the implementation would look like:
|
||||||
|
/*
|
||||||
|
import readline from 'readline';
|
||||||
|
import { db } from '@reviq/db';
|
||||||
|
import { randomBytes } from 'crypto';
|
||||||
|
|
||||||
|
const rl = readline.createInterface({
|
||||||
|
input: process.stdin,
|
||||||
|
output: process.stdout,
|
||||||
|
});
|
||||||
|
|
||||||
|
const email = await new Promise<string>((resolve) => {
|
||||||
|
rl.question('Email: ', resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
const password = await new Promise<string>((resolve) => {
|
||||||
|
rl.question('Password: ', resolve);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate a random salt
|
||||||
|
const salt = randomBytes(16);
|
||||||
|
|
||||||
|
// Hash with scrypt using recommended parameters
|
||||||
|
// N=2^14 (16384), r=8, p=1 - good balance of security and performance
|
||||||
|
const hash = scrypt(utf8ToBytes(password), salt, { N: 16384, r: 8, p: 1, dkLen: 32 });
|
||||||
|
|
||||||
|
// Store as: $scrypt$N=16384,r=8,p=1$<salt hex>$<hash hex>
|
||||||
|
const hashedPassword = `$scrypt$N=16384,r=8,p=1$${bytesToHex(salt)}$${bytesToHex(hash)}`;
|
||||||
|
|
||||||
|
await db.insertInto('users')
|
||||||
|
.values({
|
||||||
|
email: email.toLowerCase(),
|
||||||
|
password_hash: hashedPassword,
|
||||||
|
is_superuser: true,
|
||||||
|
email_verified_at: new Date(),
|
||||||
|
})
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
console.log('Superuser created successfully!');
|
||||||
|
rl.close();
|
||||||
|
*/
|
||||||
|
}
|
||||||
25
apps/cli/src/commands/org.ts
Normal file
25
apps/cli/src/commands/org.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
import type { CommandContext } from "@stricli/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create organization command stub
|
||||||
|
*/
|
||||||
|
export async function create(this: CommandContext): Promise<void> {
|
||||||
|
console.log("Org create command - Not implemented");
|
||||||
|
console.log("This command will create a new organization");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* List organizations command stub
|
||||||
|
*/
|
||||||
|
export async function list(this: CommandContext): Promise<void> {
|
||||||
|
console.log("Org list command - Not implemented");
|
||||||
|
console.log("This command will list all organizations");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add site to organization command stub
|
||||||
|
*/
|
||||||
|
export async function addSite(this: CommandContext): Promise<void> {
|
||||||
|
console.log("Org add-site command - Not implemented");
|
||||||
|
console.log("This command will add a site to an organization");
|
||||||
|
}
|
||||||
17
apps/cli/src/commands/user.ts
Normal file
17
apps/cli/src/commands/user.ts
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import type { CommandContext } from "@stricli/core";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create user command stub
|
||||||
|
*/
|
||||||
|
export async function create(this: CommandContext): Promise<void> {
|
||||||
|
console.log("User create command - Not implemented");
|
||||||
|
console.log("This command will create a new user account");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Confirm email command stub
|
||||||
|
*/
|
||||||
|
export async function confirmEmail(this: CommandContext): Promise<void> {
|
||||||
|
console.log("User confirm-email command - Not implemented");
|
||||||
|
console.log("This command will confirm a user's email address");
|
||||||
|
}
|
||||||
6
apps/cli/src/context.ts
Normal file
6
apps/cli/src/context.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
/**
|
||||||
|
* Local context for CLI application
|
||||||
|
*/
|
||||||
|
export interface LocalContext {
|
||||||
|
readonly process: NodeJS.Process;
|
||||||
|
}
|
||||||
24
apps/cli/tsconfig.json
Normal file
24
apps/cli/tsconfig.json
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"extends": "@macalinao/tsconfig/tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ES2022",
|
||||||
|
"module": "NodeNext",
|
||||||
|
"moduleResolution": "NodeNext",
|
||||||
|
"lib": ["ES2022"],
|
||||||
|
"types": ["@types/bun"],
|
||||||
|
"outDir": "./dist",
|
||||||
|
"rootDir": "./src",
|
||||||
|
"strict": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"declaration": true,
|
||||||
|
"declarationMap": true,
|
||||||
|
"sourceMap": true,
|
||||||
|
"isolatedDeclarations": false,
|
||||||
|
"composite": false
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules", "dist"]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user