Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 40 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
day: "monday"
open-pull-requests-limit: 5
groups:
mesh-sdk:
patterns:
- "@meshsdk/*"
next:
patterns:
- "next"
- "next-*"
- "@next/*"
prisma:
patterns:
- "prisma"
- "@prisma/*"
trpc:
patterns:
- "@trpc/*"
types:
patterns:
- "@types/*"
update-types:
- "minor"
- "patch"
labels:
- "dependencies"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "monthly"
labels:
- "dependencies"
- "ci"
47 changes: 47 additions & 0 deletions .github/workflows/pr-checks.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
name: PR Checks

on:
pull_request:
branches: [main]
push:
branches: [main]

concurrency:
group: pr-checks-${{ github.ref }}
cancel-in-progress: true

jobs:
checks:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: '20'
cache: 'npm'

- name: Install dependencies
run: npm ci

- name: Generate Prisma client
run: npx prisma generate

# Lint stays non-blocking until the rule set is cleaned up; tracked separately.
- name: Lint
run: npm run lint
continue-on-error: true

# Typecheck, test, and build are gates — failures must fail the PR.
- name: Type check
run: npx tsc --noEmit

- name: Test
run: npm run test:ci

- name: Build
run: npm run build
env:
SKIP_ENV_VALIDATION: 'true'
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
-- AlterTable
ALTER TABLE "Ballot" ALTER COLUMN "anchorUrls" SET DEFAULT ARRAY[]::TEXT[],
ALTER COLUMN "anchorHashes" SET DEFAULT ARRAY[]::TEXT[];

-- CreateTable
CREATE TABLE "AuditLog" (
"id" TEXT NOT NULL,
"actorAddress" TEXT,
"actorType" TEXT NOT NULL,
"action" TEXT NOT NULL,
"resourceType" TEXT,
"resourceId" TEXT,
"ip" TEXT,
"userAgent" TEXT,
"outcome" TEXT NOT NULL,
"reason" TEXT,
"metadata" JSONB,
"createdAt" TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP,

CONSTRAINT "AuditLog_pkey" PRIMARY KEY ("id")
);

-- CreateIndex
CREATE INDEX "AuditLog_actorAddress_idx" ON "AuditLog"("actorAddress");

-- CreateIndex
CREATE INDEX "AuditLog_action_idx" ON "AuditLog"("action");

-- CreateIndex
CREATE INDEX "AuditLog_resourceType_resourceId_idx" ON "AuditLog"("resourceType", "resourceId");

-- CreateIndex
CREATE INDEX "AuditLog_createdAt_idx" ON "AuditLog"("createdAt");

-- CreateIndex
CREATE INDEX "AuditLog_actorAddress_createdAt_idx" ON "AuditLog"("actorAddress", "createdAt");

-- CreateIndex
CREATE INDEX "BalanceSnapshot_walletId_idx" ON "BalanceSnapshot"("walletId");

-- CreateIndex
CREATE INDEX "BalanceSnapshot_walletId_snapshotDate_idx" ON "BalanceSnapshot"("walletId", "snapshotDate");

-- CreateIndex
CREATE INDEX "Ballot_walletId_idx" ON "Ballot"("walletId");

-- CreateIndex
CREATE INDEX "NewWallet_ownerAddress_idx" ON "NewWallet"("ownerAddress");

-- CreateIndex
CREATE INDEX "NewWallet_signersAddresses_idx" ON "NewWallet" USING GIN ("signersAddresses" array_ops);

-- CreateIndex
CREATE INDEX "Proxy_walletId_idx" ON "Proxy"("walletId");

-- CreateIndex
CREATE INDEX "Proxy_userId_idx" ON "Proxy"("userId");

-- CreateIndex
CREATE INDEX "Proxy_walletId_isActive_idx" ON "Proxy"("walletId", "isActive");

-- CreateIndex
CREATE INDEX "Proxy_userId_isActive_idx" ON "Proxy"("userId", "isActive");

-- CreateIndex
CREATE INDEX "Signable_walletId_idx" ON "Signable"("walletId");

-- CreateIndex
CREATE INDEX "Signable_state_idx" ON "Signable"("state");

-- CreateIndex
CREATE INDEX "Signable_walletId_state_idx" ON "Signable"("walletId", "state");

-- CreateIndex
CREATE INDEX "Transaction_walletId_idx" ON "Transaction"("walletId");

-- CreateIndex
CREATE INDEX "Transaction_state_idx" ON "Transaction"("state");

-- CreateIndex
CREATE INDEX "Transaction_walletId_state_idx" ON "Transaction"("walletId", "state");

-- CreateIndex
CREATE INDEX "Wallet_ownerAddress_idx" ON "Wallet"("ownerAddress");

-- CreateIndex
CREATE INDEX "Wallet_signersAddresses_idx" ON "Wallet" USING GIN ("signersAddresses" array_ops);

Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "User" ALTER COLUMN "nostrKey" DROP NOT NULL;
33 changes: 27 additions & 6 deletions prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ datasource db {
}

model User {
id String @id @default(cuid())
address String @unique
stakeAddress String @unique
drepKeyHash String @default("")
nostrKey String @unique
discordId String @default("")
id String @id @default(cuid())
address String @unique
stakeAddress String @unique
drepKeyHash String @default("")
nostrKey String? @unique
discordId String @default("")
}

model Wallet {
Expand Down Expand Up @@ -257,3 +257,24 @@ model BotClaimToken {

@@index([tokenHash])
}

model AuditLog {
id String @id @default(cuid())
actorAddress String? // Wallet address that performed the action (null for system/anonymous)
actorType String // "user" | "bot" | "system"
action String // e.g. "wallet.create", "tx.sign", "bot.grant", "auth.login"
resourceType String? // "wallet" | "transaction" | "bot" | "ballot" | etc.
resourceId String?
ip String?
userAgent String?
outcome String // "success" | "denied" | "error"
reason String? // Short reason on denied/error
metadata Json? // Additional context (no secrets, redacted)
createdAt DateTime @default(now())

@@index([actorAddress])
@@index([action])
@@index([resourceType, resourceId])
@@index([createdAt])
@@index([actorAddress, createdAt])
}
7 changes: 6 additions & 1 deletion scripts/ci/framework/markdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,12 @@ function fmtMs(ms: number): string {
}

function escapeCell(s: string): string {
return s.replace(/\\/g, "\\\\").replace(/\|/g, "\\|").replace(/\n/g, " ");
// Escape both `\` and `|` in a single pass with a character class so the
// replacement can't be mis-ordered or partially applied — CodeQL's
// js/incomplete-sanitization rule flagged the previous chained version.
// Backslash is added in the replacement, so we capture and prefix in
// one regex (no second pass that could double-escape).
return s.replace(/[\\|]/g, "\\$&").replace(/[\r\n]+/g, " ");
}

function renderSteps(steps: StepReport[]): string {
Expand Down
1 change: 1 addition & 0 deletions src/__tests__/__mocks__/styleMock.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
module.exports = {};
Loading
Loading