MVP for API Key authentication

This commit is contained in:
ian
2025-12-03 14:51:59 +07:00
parent 9413c442df
commit 347e0192e2
17 changed files with 733 additions and 49 deletions
@@ -0,0 +1,62 @@
CREATE OR REPLACE FUNCTION generate_weekly_id(p_prefix text)
RETURNS text
LANGUAGE plpgsql
AS $$
DECLARE
v_yy smallint;
v_ww smallint;
v_seq integer;
BEGIN
SELECT
EXTRACT(ISOYEAR FROM CURRENT_DATE)::smallint,
EXTRACT(WEEK FROM CURRENT_DATE)::smallint
INTO v_yy, v_ww;
INSERT INTO weekly_id_counters (prefix, yy, ww, last_value)
VALUES (p_prefix, v_yy, v_ww, 1)
ON CONFLICT (prefix, yy, ww)
DO UPDATE SET last_value = weekly_id_counters.last_value + 1
RETURNING last_value INTO v_seq;
RETURN format(
'%s-%02s%02s%04s',
p_prefix,
v_yy % 100,
v_ww,
v_seq
);
END;
$$;
--> statement-breakpoint
CREATE TABLE "merchant_nonces" (
"merchant_id" varchar(64) NOT NULL,
"nonce" varchar(128) NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
CONSTRAINT "merchant_nonces_merchant_id_nonce_pk" PRIMARY KEY("merchant_id","nonce")
);
--> statement-breakpoint
CREATE TABLE "merchants" (
"merchant_id" text PRIMARY KEY DEFAULT generate_weekly_id('MERCH') NOT NULL,
"merchant_name" text NOT NULL,
"contact_email" text NOT NULL,
"contact_phone" text NOT NULL,
"api_key" text NOT NULL,
"secret_key" text NOT NULL,
"is_active" boolean DEFAULT true NOT NULL,
"created_at" timestamp with time zone DEFAULT now() NOT NULL,
"revoked_at" timestamp with time zone
);
--> statement-breakpoint
CREATE TABLE "weekly_id_counters" (
"prefix" text NOT NULL,
"yy" smallint NOT NULL,
"ww" smallint NOT NULL,
"last_value" integer NOT NULL,
CONSTRAINT "weekly_id_counters_prefix_yy_ww_pk" PRIMARY KEY("prefix","yy","ww")
);
--> statement-breakpoint
ALTER TABLE "merchant_nonces" ADD CONSTRAINT "merchant_nonces_merchant_id_merchants_merchant_id_fk" FOREIGN KEY ("merchant_id") REFERENCES "public"."merchants"("merchant_id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
CREATE INDEX "merchants_secret_key_idx" ON "merchants" USING btree ("secret_key");--> statement-breakpoint
CREATE INDEX "merchants_is_active_idx" ON "merchants" USING btree ("is_active");--> statement-breakpoint
CREATE INDEX "merchants_contact_email_idx" ON "merchants" USING btree ("contact_email");--> statement-breakpoint
CREATE INDEX "merchants_contact_phone_idx" ON "merchants" USING btree ("contact_phone");
@@ -0,0 +1,358 @@
{
"id": "64d5c135-050b-402c-9bb5-e2eff6b17d93",
"prevId": "7f01d0a9-3f79-476c-b885-b83b3191bdf8",
"version": "7",
"dialect": "postgresql",
"tables": {
"public.merchant_nonces": {
"name": "merchant_nonces",
"schema": "",
"columns": {
"merchant_id": {
"name": "merchant_id",
"type": "varchar(64)",
"primaryKey": false,
"notNull": true
},
"nonce": {
"name": "nonce",
"type": "varchar(128)",
"primaryKey": false,
"notNull": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
}
},
"indexes": {},
"foreignKeys": {
"merchant_nonces_merchant_id_merchants_merchant_id_fk": {
"name": "merchant_nonces_merchant_id_merchants_merchant_id_fk",
"tableFrom": "merchant_nonces",
"tableTo": "merchants",
"columnsFrom": [
"merchant_id"
],
"columnsTo": [
"merchant_id"
],
"onDelete": "cascade",
"onUpdate": "no action"
}
},
"compositePrimaryKeys": {
"merchant_nonces_merchant_id_nonce_pk": {
"name": "merchant_nonces_merchant_id_nonce_pk",
"columns": [
"merchant_id",
"nonce"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.merchants": {
"name": "merchants",
"schema": "",
"columns": {
"merchant_id": {
"name": "merchant_id",
"type": "text",
"primaryKey": true,
"notNull": true,
"default": "generate_weekly_id('MERCH')"
},
"merchant_name": {
"name": "merchant_name",
"type": "text",
"primaryKey": false,
"notNull": true
},
"contact_email": {
"name": "contact_email",
"type": "text",
"primaryKey": false,
"notNull": true
},
"contact_phone": {
"name": "contact_phone",
"type": "text",
"primaryKey": false,
"notNull": true
},
"api_key": {
"name": "api_key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"secret_key": {
"name": "secret_key",
"type": "text",
"primaryKey": false,
"notNull": true
},
"is_active": {
"name": "is_active",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"revoked_at": {
"name": "revoked_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
}
},
"indexes": {
"merchants_secret_key_idx": {
"name": "merchants_secret_key_idx",
"columns": [
{
"expression": "secret_key",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"merchants_is_active_idx": {
"name": "merchants_is_active_idx",
"columns": [
{
"expression": "is_active",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"merchants_contact_email_idx": {
"name": "merchants_contact_email_idx",
"columns": [
{
"expression": "contact_email",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
},
"merchants_contact_phone_idx": {
"name": "merchants_contact_phone_idx",
"columns": [
{
"expression": "contact_phone",
"isExpression": false,
"asc": true,
"nulls": "last"
}
],
"isUnique": false,
"concurrently": false,
"method": "btree",
"with": {}
}
},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.users": {
"name": "users",
"schema": "",
"columns": {
"id": {
"name": "id",
"type": "serial",
"primaryKey": true,
"notNull": true
},
"name": {
"name": "name",
"type": "varchar(255)",
"primaryKey": false,
"notNull": true
},
"email": {
"name": "email",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"phone": {
"name": "phone",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"password": {
"name": "password",
"type": "varchar(127)",
"primaryKey": false,
"notNull": false
},
"image": {
"name": "image",
"type": "text",
"primaryKey": false,
"notNull": false
},
"google_id": {
"name": "google_id",
"type": "varchar(255)",
"primaryKey": false,
"notNull": false
},
"role": {
"name": "role",
"type": "role",
"typeSchema": "public",
"primaryKey": false,
"notNull": true,
"default": "'user'"
},
"status": {
"name": "status",
"type": "boolean",
"primaryKey": false,
"notNull": true,
"default": true
},
"created_at": {
"name": "created_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true,
"default": "now()"
},
"updated_at": {
"name": "updated_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": true
},
"deleted_at": {
"name": "deleted_at",
"type": "timestamp with time zone",
"primaryKey": false,
"notNull": false
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {},
"uniqueConstraints": {
"users_email_unique": {
"name": "users_email_unique",
"nullsNotDistinct": false,
"columns": [
"email"
]
}
},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
},
"public.weekly_id_counters": {
"name": "weekly_id_counters",
"schema": "",
"columns": {
"prefix": {
"name": "prefix",
"type": "text",
"primaryKey": false,
"notNull": true
},
"yy": {
"name": "yy",
"type": "smallint",
"primaryKey": false,
"notNull": true
},
"ww": {
"name": "ww",
"type": "smallint",
"primaryKey": false,
"notNull": true
},
"last_value": {
"name": "last_value",
"type": "integer",
"primaryKey": false,
"notNull": true
}
},
"indexes": {},
"foreignKeys": {},
"compositePrimaryKeys": {
"weekly_id_counters_prefix_yy_ww_pk": {
"name": "weekly_id_counters_prefix_yy_ww_pk",
"columns": [
"prefix",
"yy",
"ww"
]
}
},
"uniqueConstraints": {},
"policies": {},
"checkConstraints": {},
"isRLSEnabled": false
}
},
"enums": {
"public.role": {
"name": "role",
"schema": "public",
"values": [
"admin",
"visitor",
"user",
"volunteer"
]
}
},
"schemas": {},
"sequences": {},
"roles": {},
"policies": {},
"views": {},
"_meta": {
"columns": {},
"schemas": {},
"tables": {}
}
}
+7
View File
@@ -8,6 +8,13 @@
"when": 1764313178750,
"tag": "1764313178_flimsy_tarantula",
"breakpoints": true
},
{
"idx": 1,
"version": "7",
"when": 1764747189387,
"tag": "1764747189_brave_kree",
"breakpoints": true
}
]
}
+6
View File
@@ -1,4 +1,6 @@
import users, * as userSchema from './schema/users'
import merchants, * as merchantSchema from './schema/merchants'
import merchantNonces, * as merchantNonceSchema from './schema/merchant_nonces'
// relation
// import * as relations from './relations'
@@ -6,10 +8,14 @@ import users, * as userSchema from './schema/users'
export const schema = {
// ...relations,
...userSchema,
...merchantSchema,
...merchantNonceSchema,
}
export const table = {
users,
merchants,
merchantNonces,
} as const
export type Table = typeof table
+15
View File
@@ -0,0 +1,15 @@
// db/schema.ts
import { pgTable, varchar, timestamp, primaryKey } from 'drizzle-orm/pg-core'
import merchants from './merchants'
export const merchantNonces = pgTable('merchant_nonces', {
merchantId: varchar('merchant_id', { length: 64 })
.notNull()
.references(() => merchants.merchantId, { onDelete: 'cascade' }),
nonce: varchar('nonce', { length: 128 }).notNull(),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
}, (t) => [
primaryKey({ columns: [t.merchantId, t.nonce] }),
])
export default merchantNonces
+22
View File
@@ -0,0 +1,22 @@
// db/schema.ts
import { sql } from 'drizzle-orm'
import { pgTable, text, boolean, timestamp, index } from 'drizzle-orm/pg-core'
export const merchants = pgTable('merchants', {
merchantId: text('merchant_id').primaryKey().notNull().default(sql<string>`generate_weekly_id('MERCH')`),
merchantName: text('merchant_name').notNull(),
contactEmail: text('contact_email').notNull(),
contactPhone: text('contact_phone').notNull(),
apiKey: text('api_key').notNull(),
secretKey: text('secret_key').notNull(),
isActive: boolean('is_active').notNull().default(true),
createdAt: timestamp('created_at', { withTimezone: true }).notNull().defaultNow(),
revokedAt: timestamp('revoked_at', { withTimezone: true }),
}, (t) => [
index('merchants_secret_key_idx').on(t.secretKey),
index('merchants_is_active_idx').on(t.isActive),
index('merchants_contact_email_idx').on(t.contactEmail),
index('merchants_contact_phone_idx').on(t.contactPhone),
])
export default merchants
+14
View File
@@ -0,0 +1,14 @@
import { integer, pgTable, primaryKey, smallint, text } from 'drizzle-orm/pg-core'
export const weeklyIdCounters = pgTable(
'weekly_id_counters',
{
prefix: text('prefix').notNull(),
yy: smallint('yy').notNull(),
ww: smallint('ww').notNull(),
lastValue: integer('last_value').notNull(),
},
(t) => [
primaryKey({ columns: [t.prefix, t.yy, t.ww] }),
],
)