update
This commit is contained in:
@@ -34,14 +34,20 @@ const authLimiter = rateLimit({
|
||||
});
|
||||
|
||||
app.use('/api/auth', authLimiter);
|
||||
// General rate limiter — skip auth routes (they have their own limiter above)
|
||||
// General rate limiter — skip auth routes (own limiter above) and authenticated
|
||||
// requests (Bearer token present). Auth middleware validates the token downstream;
|
||||
// rate-limiting authenticated dashboard polling would cause 429 floods.
|
||||
app.use('/api', rateLimit({
|
||||
windowMs: environment.rateLimit.windowMs,
|
||||
max: environment.rateLimit.max,
|
||||
message: 'Too many requests from this IP, please try again later.',
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
skip: (req) => req.path.startsWith('/auth'),
|
||||
skip: (req) => {
|
||||
if (req.path.startsWith('/auth')) return true;
|
||||
const auth = req.headers.authorization;
|
||||
return typeof auth === 'string' && auth.startsWith('Bearer ');
|
||||
},
|
||||
}));
|
||||
|
||||
// Body parsing middleware
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
-- Remove mitglieds_nr column (replaced by fdisk_standesbuch_nr as the canonical member number)
|
||||
ALTER TABLE mitglieder_profile DROP COLUMN IF EXISTS mitglieds_nr;
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Migration 032: Add FDISK-scraped profile fields to mitglieder_profile
|
||||
ALTER TABLE mitglieder_profile ADD COLUMN IF NOT EXISTS geburtsort VARCHAR(128);
|
||||
ALTER TABLE mitglieder_profile ADD COLUMN IF NOT EXISTS geschlecht VARCHAR(1);
|
||||
ALTER TABLE mitglieder_profile ADD COLUMN IF NOT EXISTS beruf VARCHAR(255);
|
||||
ALTER TABLE mitglieder_profile ADD COLUMN IF NOT EXISTS wohnort VARCHAR(128);
|
||||
ALTER TABLE mitglieder_profile ADD COLUMN IF NOT EXISTS plz VARCHAR(16);
|
||||
@@ -0,0 +1,13 @@
|
||||
-- Migration 033: Create befoerderungen table (FDISK sync)
|
||||
CREATE TABLE IF NOT EXISTS befoerderungen (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
datum DATE,
|
||||
dienstgrad VARCHAR(64) NOT NULL,
|
||||
fdisk_sync_key VARCHAR(255),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(user_id, fdisk_sync_key)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_befoerderungen_user_id ON befoerderungen(user_id);
|
||||
@@ -0,0 +1,16 @@
|
||||
-- Migration 034: Create untersuchungen table (FDISK sync)
|
||||
CREATE TABLE IF NOT EXISTS untersuchungen (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
datum DATE,
|
||||
anmerkungen TEXT,
|
||||
art VARCHAR(128) NOT NULL,
|
||||
ergebnis VARCHAR(128),
|
||||
fdisk_sync_key VARCHAR(255),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(user_id, fdisk_sync_key)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_untersuchungen_user_id ON untersuchungen(user_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_untersuchungen_art ON untersuchungen(user_id, art);
|
||||
@@ -0,0 +1,16 @@
|
||||
-- Migration 035: Create fahrgenehmigungen table (FDISK sync)
|
||||
CREATE TABLE IF NOT EXISTS fahrgenehmigungen (
|
||||
id UUID PRIMARY KEY DEFAULT uuid_generate_v4(),
|
||||
user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE,
|
||||
ausstellungsdatum DATE,
|
||||
gueltig_bis DATE,
|
||||
behoerde VARCHAR(128),
|
||||
nummer VARCHAR(64),
|
||||
klasse VARCHAR(128) NOT NULL,
|
||||
fdisk_sync_key VARCHAR(255),
|
||||
created_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ DEFAULT NOW(),
|
||||
UNIQUE(user_id, fdisk_sync_key)
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_fahrgenehmigungen_user_id ON fahrgenehmigungen(user_id);
|
||||
@@ -63,7 +63,6 @@ export interface MitgliederProfile {
|
||||
id: string;
|
||||
user_id: string;
|
||||
|
||||
mitglieds_nr: string | null;
|
||||
fdisk_standesbuch_nr: string | null;
|
||||
dienstgrad: DienstgradEnum | null;
|
||||
dienstgrad_seit: Date | null;
|
||||
@@ -86,6 +85,13 @@ export interface MitgliederProfile {
|
||||
bemerkungen: string | null;
|
||||
bild_url: string | null;
|
||||
|
||||
// FDISK-synced extended profile fields
|
||||
geburtsort: string | null;
|
||||
geschlecht: string | null;
|
||||
beruf: string | null;
|
||||
wohnort: string | null;
|
||||
plz: string | null;
|
||||
|
||||
created_at: Date;
|
||||
updated_at: Date;
|
||||
}
|
||||
@@ -143,7 +149,6 @@ export interface MemberListItem {
|
||||
|
||||
// profile fields (null when no profile exists)
|
||||
profile_id: string | null;
|
||||
mitglieds_nr: string | null;
|
||||
fdisk_standesbuch_nr: string | null;
|
||||
dienstgrad: DienstgradEnum | null;
|
||||
funktion: FunktionEnum[];
|
||||
@@ -156,7 +161,7 @@ export interface MemberListItem {
|
||||
// Filter parameters for getAllMembers()
|
||||
// ============================================================
|
||||
export interface MemberFilters {
|
||||
search?: string; // matches name, email, mitglieds_nr
|
||||
search?: string; // matches name, email, fdisk_standesbuch_nr
|
||||
status?: StatusEnum[];
|
||||
dienstgrad?: DienstgradEnum[];
|
||||
page?: number; // 1-based
|
||||
@@ -173,7 +178,6 @@ export interface MemberFilters {
|
||||
* status has a default; every other field may be omitted.
|
||||
*/
|
||||
export const CreateMemberProfileSchema = z.object({
|
||||
mitglieds_nr: z.string().max(32).optional(),
|
||||
fdisk_standesbuch_nr: z.string().max(32).optional(),
|
||||
dienstgrad: z.enum(DIENSTGRAD_VALUES).optional(),
|
||||
dienstgrad_seit: z.coerce.date().optional(),
|
||||
|
||||
@@ -36,7 +36,6 @@ class MemberService {
|
||||
-- profile columns (aliased with mp_ prefix to avoid collision)
|
||||
mp.id AS mp_id,
|
||||
mp.user_id AS mp_user_id,
|
||||
mp.mitglieds_nr AS mp_mitglieds_nr,
|
||||
mp.fdisk_standesbuch_nr AS mp_fdisk_standesbuch_nr,
|
||||
mp.dienstgrad AS mp_dienstgrad,
|
||||
mp.dienstgrad_seit AS mp_dienstgrad_seit,
|
||||
@@ -54,6 +53,11 @@ class MemberService {
|
||||
mp.schuhgroesse AS mp_schuhgroesse,
|
||||
mp.bemerkungen AS mp_bemerkungen,
|
||||
mp.bild_url AS mp_bild_url,
|
||||
mp.geburtsort AS mp_geburtsort,
|
||||
mp.geschlecht AS mp_geschlecht,
|
||||
mp.beruf AS mp_beruf,
|
||||
mp.wohnort AS mp_wohnort,
|
||||
mp.plz AS mp_plz,
|
||||
mp.created_at AS mp_created_at,
|
||||
mp.updated_at AS mp_updated_at
|
||||
FROM users u
|
||||
@@ -83,7 +87,6 @@ class MemberService {
|
||||
? {
|
||||
id: row.mp_id,
|
||||
user_id: row.mp_user_id,
|
||||
mitglieds_nr: row.mp_mitglieds_nr,
|
||||
fdisk_standesbuch_nr: row.mp_fdisk_standesbuch_nr ?? null,
|
||||
dienstgrad: row.mp_dienstgrad,
|
||||
dienstgrad_seit: row.mp_dienstgrad_seit,
|
||||
@@ -101,6 +104,11 @@ class MemberService {
|
||||
schuhgroesse: row.mp_schuhgroesse,
|
||||
bemerkungen: row.mp_bemerkungen,
|
||||
bild_url: row.mp_bild_url,
|
||||
geburtsort: row.mp_geburtsort ?? null,
|
||||
geschlecht: row.mp_geschlecht ?? null,
|
||||
beruf: row.mp_beruf ?? null,
|
||||
wohnort: row.mp_wohnort ?? null,
|
||||
plz: row.mp_plz ?? null,
|
||||
created_at: row.mp_created_at,
|
||||
updated_at: row.mp_updated_at,
|
||||
}
|
||||
@@ -137,7 +145,6 @@ class MemberService {
|
||||
OR u.email ILIKE $${paramIdx}
|
||||
OR u.given_name ILIKE $${paramIdx}
|
||||
OR u.family_name ILIKE $${paramIdx}
|
||||
OR mp.mitglieds_nr ILIKE $${paramIdx}
|
||||
)`);
|
||||
values.push(`%${search}%`);
|
||||
paramIdx++;
|
||||
@@ -168,7 +175,6 @@ class MemberService {
|
||||
u.profile_picture_url,
|
||||
u.is_active,
|
||||
mp.id AS profile_id,
|
||||
mp.mitglieds_nr,
|
||||
mp.fdisk_standesbuch_nr,
|
||||
mp.dienstgrad,
|
||||
mp.funktion,
|
||||
@@ -204,7 +210,6 @@ class MemberService {
|
||||
profile_picture_url: row.profile_picture_url,
|
||||
is_active: row.is_active,
|
||||
profile_id: row.profile_id ?? null,
|
||||
mitglieds_nr: row.mitglieds_nr ?? null,
|
||||
fdisk_standesbuch_nr: row.fdisk_standesbuch_nr ?? null,
|
||||
dienstgrad: row.dienstgrad ?? null,
|
||||
funktion: row.funktion ?? [],
|
||||
@@ -286,7 +291,6 @@ class MemberService {
|
||||
const query = `
|
||||
INSERT INTO mitglieder_profile (
|
||||
user_id,
|
||||
mitglieds_nr,
|
||||
fdisk_standesbuch_nr,
|
||||
dienstgrad,
|
||||
dienstgrad_seit,
|
||||
@@ -306,14 +310,13 @@ class MemberService {
|
||||
bild_url
|
||||
) VALUES (
|
||||
$1, $2, $3, $4, $5, $6, $7, $8, $9, $10,
|
||||
$11, $12, $13, $14, $15, $16, $17, $18, $19
|
||||
$11, $12, $13, $14, $15, $16, $17, $18
|
||||
)
|
||||
RETURNING *
|
||||
`;
|
||||
|
||||
const values = [
|
||||
userId,
|
||||
data.mitglieds_nr ?? null,
|
||||
data.fdisk_standesbuch_nr ?? null,
|
||||
data.dienstgrad ?? null,
|
||||
data.dienstgrad_seit ?? null,
|
||||
@@ -392,7 +395,6 @@ class MemberService {
|
||||
let paramIdx = 1;
|
||||
|
||||
const fieldMap: Record<string, any> = {
|
||||
mitglieds_nr: rest.mitglieds_nr,
|
||||
fdisk_standesbuch_nr: rest.fdisk_standesbuch_nr,
|
||||
funktion: rest.funktion,
|
||||
status: rest.status,
|
||||
|
||||
Reference in New Issue
Block a user