Skip to content

Refactor: Extract duplicated CMS collection patterns into shared utilities - #121

Merged
marcelo-m7 merged 3 commits into
prodfrom
copilot/refactor-duplicated-code-again
Nov 1, 2025
Merged

marcelo-m7 merged 3 commits into
prodfrom
copilot/refactor-duplicated-code-again

Conversation

Copilot AI commented Nov 1, 2025 •

Copy link
Copy Markdown
Contributor

CMS collection files contained ~400 lines of duplicated code across database operations, validation, and access control patterns.

Changes

New Utilities

cms/src/utilities/validation.ts

  • normalizeUrl(), normalizeEmail() - Previously duplicated in 3 and 2 files respectively
  • extractTags(), extractFeatureList(), serializeValue() - Common data extraction patterns

cms/src/utilities/upsert-factory.ts

  • createUpsertHook() - Factory replacing 50-100 lines of boilerplate per collection
  • Handles ID/non-ID inserts, JSONB casting, error handling, and Supabase ID backfill

cms/src/utilities/collection-helpers.ts

  • Shared access control patterns: publicReadAuthWrite, adminOnlyAccess
  • Common field definitions: supabaseIdField, activeField

Updated Collections

Refactored Solutions, Repositories, HomepageFeatures, TeamMembers, Leads, NewsletterSubscribers, and SiteSettings to use shared utilities.

Before:

const normalizeUrl = (value: unknown): string | null => { /* 10 lines */ };

const upsertIntoSupabase = async ({ doc, req }) => {
  const supabaseId = doc.supabaseId ?? null;
  // 80+ lines of query building, error handling, logging...
};

const Collection: CollectionConfig = {
  access: {
    read: () => true,
    create: ({ req }) => Boolean(req.user),
    // ...
  },
  fields: [
    { name: 'supabaseId', type: 'text', admin: { hidden: true } },
    // ...
  ],
};

After:

import { normalizeUrl } from '../utilities/validation';
import { createUpsertHook } from '../utilities/upsert-factory';
import { publicReadAuthWrite, supabaseIdField } from '../utilities/collection-helpers';

const upsertIntoSupabase = createUpsertHook({
  collectionName: 'repositories',
  tableName: 'repositories',
  extractData: (doc) => ({
    name: resolveLocalizedText(doc.name),
    github_url: normalizeUrl(doc.githubUrl),
    // ...
  }),
});

const Collection: CollectionConfig = {
  access: publicReadAuthWrite,
  fields: [supabaseIdField, /* ... */],
};

Impact

  • -300 net lines (783 removed, 483 added)
  • Database operation changes now propagate to all collections from single source
  • Validation logic centralized and testable independently
Original prompt

Find and refactor duplicated code


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits November 1, 2025 20:13
- Created shared validation utilities (normalizeUrl, normalizeEmail, extractTags, extractFeatureList, serializeValue)
- Created upsert factory to eliminate 50-100 line duplication across collections
- Created shared access control and field definitions
- Updated 7 collection files (Solutions, Repositories, HomepageFeatures, TeamMembers, Leads, NewsletterSubscribers, SiteSettings)
- All tests passing, linter clean

Co-authored-by: marcelo-m7 <117441129+marcelo-m7@users.noreply.github.com>
Co-authored-by: marcelo-m7 <117441129+marcelo-m7@users.noreply.github.com>
Copilot AI changed the title [WIP] Refactor duplicated code for improved maintainability Refactor: Extract duplicated CMS collection patterns into shared utilities Nov 1, 2025
Copilot AI requested a review from marcelo-m7 November 1, 2025 20:16
@marcelo-m7
marcelo-m7 marked this pull request as ready for review November 1, 2025 20:26

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +64 to +85
if (supabaseId) {
// Insert with existing ID
const placeholders = buildPlaceholders(2);
query = `
insert into public.${tableName} (id, ${insertFields})
values ($1::uuid, ${placeholders})
on conflict (${conflictFieldWithId}) do update set
${updateFields},
updated_at=now()
returning id`;
params = [supabaseId, ...fieldValues];
} else {
// Insert without ID
const placeholders = buildPlaceholders(1);
const conflictField = conflictFieldWithoutId || conflictFieldWithId;
query = `
insert into public.${tableName} (${insertFields})
values (${placeholders})
on conflict (${conflictField}) do update set
${updateFields},
updated_at=now()
returning id`;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Avoid referencing missing updated_at columns in upserts

The new createUpsertHook always appends updated_at=now() to the ON CONFLICT … DO UPDATE clause. Several collections that now use this factory (e.g. Leads, NewsletterSubscribers) write to tables that only define created_at and have no updated_at column in the migrations. Running their hooks will therefore raise column "updated_at" of relation … does not exist, preventing new leads/subscribers from syncing and blocking the Supabase ID backfill. Consider making the timestamp update conditional or configurable per table.

Useful? React with 👍 / 👎.

@marcelo-m7
marcelo-m7 merged commit 7639247 into prod Nov 1, 2025
4 of 8 checks passed
@marcelo-m7
marcelo-m7 deleted the copilot/refactor-duplicated-code-again branch November 1, 2025 20:42
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants