ReviewOS

also looking at this

stacks/analyticshq

fix(dashboard): stop remote commands importing an absent config module

#59
Merged glennmichael123 wants to merge fix/analyticshq-remote-and-subscribers into main
5 files +114 -0
app/Actions/Dashboard/Remote/RemoteCommandIndexAction.tsadded+21-0
Changes to app/Actions/Dashboard/Remote/RemoteCommandIndexAction.ts
@@ -0,0 +1,21 @@
1import { Action } from '@stacksjs/actions'
2import { response } from '@stacksjs/router'
3
4/**
5 * AnalyticsHQ does not configure dashboard-managed SSH hosts or commands.
6 *
7 * Keeping this application override prevents the framework default from
8 * importing the optional `~/config/remote` module while still giving the
9 * dashboard a valid, empty registry.
10 */
11export default new Action({
12 name: 'RemoteCommandIndexAction',
13 description: 'List the remote commands configured for AnalyticsHQ',
14 method: 'GET',
15 async handle() {
16 return response.json({
17 hosts: [],
18 commands: [],
19 })
20 },
21})
app/Actions/Dashboard/Remote/RemoteCommandRunAction.tsadded+16-0
Changes to app/Actions/Dashboard/Remote/RemoteCommandRunAction.ts
@@ -0,0 +1,16 @@
1import { Action } from '@stacksjs/actions'
2import { response } from '@stacksjs/router'
3
4/**
5 * Fail closed because AnalyticsHQ has no remote-command registry or gate.
6 */
7export default new Action({
8 name: 'RemoteCommandRunAction',
9 description: 'Reject remote commands because AnalyticsHQ does not configure them',
10 method: 'POST',
11 async handle() {
12 return response.json({
13 message: 'Remote commands are not configured for AnalyticsHQ.',
14 }, 403)
15 },
16})
database/migrations/0000000053-create-email-subscriber-tables.sqladded+27-0
Changes to database/migrations/0000000053-create-email-subscriber-tables.sql
@@ -0,0 +1,27 @@
1-- AnalyticsHQ enables the framework newsletter routes, but its application
2-- migration corpus predates the framework Subscriber models. Keep the schema
3-- here so a clean PostgreSQL database can serve the public signup action.
4CREATE TABLE IF NOT EXISTS "subscribers" (
5 "id" BIGSERIAL PRIMARY KEY,
6 "email" varchar(255) NOT NULL,
7 "status" varchar(32) NOT NULL DEFAULT 'subscribed' CHECK ("status" IN ('subscribed', 'unsubscribed', 'pending', 'bounced')),
8 "source" varchar(100) DEFAULT 'homepage',
9 "unsubscribed_at" timestamp,
10 "user_id" bigint REFERENCES "users"("id"),
11 "created_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
12 "updated_at" timestamp,
13 "uuid" varchar(255)
14);
15CREATE UNIQUE INDEX IF NOT EXISTS "subscribers_email_unique" ON "subscribers" ("email");
16CREATE UNIQUE INDEX IF NOT EXISTS "subscribers_uuid_unique" ON "subscribers" ("uuid");
17
18CREATE TABLE IF NOT EXISTS "subscriber_emails" (
19 "id" BIGSERIAL PRIMARY KEY,
20 "email" varchar(255) NOT NULL,
21 "source" varchar(100) DEFAULT 'homepage',
22 "subscriber_id" bigint REFERENCES "subscribers"("id"),
23 "created_at" timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
24 "updated_at" timestamp,
25 "uuid" varchar(255)
26);
27CREATE UNIQUE INDEX IF NOT EXISTS "subscriber_emails_uuid_unique" ON "subscriber_emails" ("uuid");
tests/unit/remote-command-actions.test.tsadded+24-0
Changes to tests/unit/remote-command-actions.test.ts
@@ -0,0 +1,24 @@
1import { describe, expect, it } from 'bun:test'
2import RemoteCommandIndexAction from '../../app/Actions/Dashboard/Remote/RemoteCommandIndexAction'
3import RemoteCommandRunAction from '../../app/Actions/Dashboard/Remote/RemoteCommandRunAction'
4
5describe('dashboard remote commands', () => {
6 it('loads without the optional framework remote config alias', async () => {
7 const result = await RemoteCommandIndexAction.handle()
8
9 expect(result.status).toBe(200)
10 expect(await result.json()).toEqual({
11 hosts: [],
12 commands: [],
13 })
14 })
15
16 it('fails closed when a remote command run is requested', async () => {
17 const result = await RemoteCommandRunAction.handle()
18
19 expect(result.status).toBe(403)
20 expect(await result.json()).toEqual({
21 message: 'Remote commands are not configured for AnalyticsHQ.',
22 })
23 })
24})
tests/unit/subscriber-migration.test.tsadded+26-0
Changes to tests/unit/subscriber-migration.test.ts
@@ -0,0 +1,26 @@
1import { describe, expect, it } from 'bun:test'
2import { readFileSync } from 'node:fs'
3import { join } from 'node:path'
4
5const migration = readFileSync(
6 join(import.meta.dir, '../../database/migrations/0000000053-create-email-subscriber-tables.sql'),
7 'utf8',
8)
9
10describe('email subscriber migration', () => {
11 it('creates both tables needed by SubscriberEmailAction', () => {
12 expect(migration).toContain('CREATE TABLE IF NOT EXISTS "subscribers"')
13 expect(migration).toContain('CREATE TABLE IF NOT EXISTS "subscriber_emails"')
14 expect(migration.indexOf('"subscribers"')).toBeLessThan(migration.indexOf('"subscriber_emails"'))
15 })
16
17 it('keeps subscriber identity and lookup fields unique', () => {
18 expect(migration).toContain('"subscribers_email_unique"')
19 expect(migration).toContain('"subscribers_uuid_unique"')
20 expect(migration).toContain('"subscriber_emails_uuid_unique"')
21 })
22
23 it('connects subscriber emails to their subscriber', () => {
24 expect(migration).toContain('"subscriber_id" bigint REFERENCES "subscribers"("id")')
25 })
26})