← back to Cypress Awards

backend/src/models/Organization.js

226 lines

const { query } = require('./database');

class Organization {
  // Create new organization
  static async create(orgData) {
    const {
      name,
      website_url,
      cypres_page_url,
      mission_statement,
      logo_url,
      ein,
      founded_year,
      location_city,
      location_state,
      location_country = 'USA'
    } = orgData;

    const sql = `
      INSERT INTO organizations
      (name, website_url, cypres_page_url, mission_statement, logo_url, ein,
       founded_year, location_city, location_state, location_country)
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
      ON CONFLICT (website_url)
      DO UPDATE SET
        name = EXCLUDED.name,
        cypres_page_url = EXCLUDED.cypres_page_url,
        mission_statement = EXCLUDED.mission_statement,
        logo_url = EXCLUDED.logo_url,
        ein = EXCLUDED.ein,
        founded_year = EXCLUDED.founded_year,
        location_city = EXCLUDED.location_city,
        location_state = EXCLUDED.location_state,
        location_country = EXCLUDED.location_country,
        updated_at = CURRENT_TIMESTAMP,
        last_verified = CURRENT_TIMESTAMP
      RETURNING *
    `;

    const result = await query(sql, [
      name, website_url, cypres_page_url, mission_statement, logo_url,
      ein, founded_year, location_city, location_state, location_country
    ]);

    return result.rows[0];
  }

  // Get organization by ID
  static async findById(id) {
    const sql = 'SELECT * FROM organizations WHERE id = $1';
    const result = await query(sql, [id]);
    return result.rows[0];
  }

  // Search organizations with filters
  static async search(filters = {}) {
    const {
      searchTerm,
      legalTopics,
      categories,
      state,
      limit = 50,
      offset = 0
    } = filters;

    let sql = `
      SELECT DISTINCT o.*,
        COALESCE(array_agg(DISTINCT c.name) FILTER (WHERE c.name IS NOT NULL), ARRAY[]::text[]) as categories,
        COALESCE(array_agg(DISTINCT lt.name) FILTER (WHERE lt.name IS NOT NULL), ARRAY[]::text[]) as legal_topics
      FROM organizations o
      LEFT JOIN organization_categories oc ON o.id = oc.organization_id
      LEFT JOIN categories c ON oc.category_id = c.id
      LEFT JOIN organization_legal_topics olt ON o.id = olt.organization_id
      LEFT JOIN legal_topics lt ON olt.legal_topic_id = lt.id
      WHERE o.is_active = true
    `;

    const params = [];
    let paramCount = 1;

    if (searchTerm) {
      sql += ` AND (
        to_tsvector('english', o.name) @@ plainto_tsquery('english', $${paramCount})
        OR to_tsvector('english', o.mission_statement) @@ plainto_tsquery('english', $${paramCount})
      )`;
      params.push(searchTerm);
      paramCount++;
    }

    if (legalTopics && legalTopics.length > 0) {
      sql += ` AND EXISTS (
        SELECT 1 FROM organization_legal_topics olt2
        JOIN legal_topics lt2 ON olt2.legal_topic_id = lt2.id
        WHERE olt2.organization_id = o.id
        AND lt2.name = ANY($${paramCount})
      )`;
      params.push(legalTopics);
      paramCount++;
    }

    if (categories && categories.length > 0) {
      sql += ` AND EXISTS (
        SELECT 1 FROM organization_categories oc2
        JOIN categories c2 ON oc2.category_id = c2.id
        WHERE oc2.organization_id = o.id
        AND c2.name = ANY($${paramCount})
      )`;
      params.push(categories);
      paramCount++;
    }

    if (state) {
      sql += ` AND o.location_state = $${paramCount}`;
      params.push(state);
      paramCount++;
    }

    sql += `
      GROUP BY o.id
      ORDER BY o.name
      LIMIT $${paramCount} OFFSET $${paramCount + 1}
    `;
    params.push(limit, offset);

    const result = await query(sql, params);
    return result.rows;
  }

  // Add categories to organization
  static async addCategories(orgId, categoryNames) {
    for (const categoryName of categoryNames) {
      const categorySql = 'SELECT id FROM categories WHERE name = $1';
      const categoryResult = await query(categorySql, [categoryName]);

      if (categoryResult.rows.length > 0) {
        const categoryId = categoryResult.rows[0].id;
        const linkSql = `
          INSERT INTO organization_categories (organization_id, category_id)
          VALUES ($1, $2)
          ON CONFLICT (organization_id, category_id) DO NOTHING
        `;
        await query(linkSql, [orgId, categoryId]);
      }
    }
  }

  // Add legal topics to organization
  static async addLegalTopics(orgId, legalTopicNames) {
    for (const topicName of legalTopicNames) {
      const topicSql = 'SELECT id FROM legal_topics WHERE name = $1';
      const topicResult = await query(topicSql, [topicName]);

      if (topicResult.rows.length > 0) {
        const topicId = topicResult.rows[0].id;
        const linkSql = `
          INSERT INTO organization_legal_topics (organization_id, legal_topic_id)
          VALUES ($1, $2)
          ON CONFLICT (organization_id, legal_topic_id) DO NOTHING
        `;
        await query(linkSql, [orgId, topicId]);
      }
    }
  }

  // Save cy pres statement
  static async saveCyPresStatement(orgId, statementData) {
    const { statement_text, page_title, html_content } = statementData;

    const sql = `
      INSERT INTO cypres_statements (organization_id, statement_text, page_title, html_content)
      VALUES ($1, $2, $3, $4)
      ON CONFLICT (organization_id)
      DO UPDATE SET
        statement_text = EXCLUDED.statement_text,
        page_title = EXCLUDED.page_title,
        html_content = EXCLUDED.html_content,
        updated_at = CURRENT_TIMESTAMP
      RETURNING *
    `;

    const result = await query(sql, [orgId, statement_text, page_title, html_content]);
    return result.rows[0];
  }

  // Get all categories (only those with active organizations)
  static async getAllCategories() {
    const sql = `
      SELECT DISTINCT c.id, c.name, c.description, COUNT(oc.organization_id) as org_count
      FROM categories c
      INNER JOIN organization_categories oc ON c.id = oc.category_id
      INNER JOIN organizations o ON oc.organization_id = o.id
      WHERE o.is_active = true
      GROUP BY c.id, c.name, c.description
      HAVING COUNT(oc.organization_id) > 0
      ORDER BY c.name
    `;
    const result = await query(sql);
    return result.rows;
  }

  // Get all legal topics (only those with active organizations)
  static async getAllLegalTopics() {
    const sql = `
      SELECT DISTINCT lt.id, lt.name, lt.description, COUNT(olt.organization_id) as org_count
      FROM legal_topics lt
      INNER JOIN organization_legal_topics olt ON lt.id = olt.legal_topic_id
      INNER JOIN organizations o ON olt.organization_id = o.id
      WHERE o.is_active = true
      GROUP BY lt.id, lt.name, lt.description
      HAVING COUNT(olt.organization_id) > 0
      ORDER BY lt.name
    `;
    const result = await query(sql);
    return result.rows;
  }

  // Get organization count
  static async getCount() {
    const sql = 'SELECT COUNT(*) as count FROM organizations WHERE is_active = true';
    const result = await query(sql);
    return parseInt(result.rows[0].count);
  }
}

module.exports = Organization;