• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

super3 / lowerproptax / 19507196835

19 Nov 2025 03:41PM UTC coverage: 95.0% (-5.0%) from 100.0%
19507196835

push

github

super3
Add monochrome icons to admin actions and user email display

- Replace solid button styles with monochrome icon-based design
- Add pencil icon for Edit and eye icon for View actions
- Update admin property page to fetch and display user email from Clerk
- Position email to right of Property Details heading for easy reference
- Add Clerk API integration to fetch user email from user_id

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

74 of 80 branches covered (92.5%)

Branch coverage included in aggregate %.

4 of 11 new or added lines in 1 file covered. (36.36%)

173 of 180 relevant lines covered (96.11%)

6.7 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

87.25
/src/controllers/adminController.js
1
import pool from '../db/connection.js';
2

3
// Get all pending properties (status = 'preparing')
4
export async function getPendingProperties(req, res) {
5
  try {
5✔
6
    const query = `
5✔
7
      SELECT
8
        p.id,
9
        p.address,
10
        p.city,
11
        p.state,
12
        p.zip_code,
13
        a.status,
14
        p.created_at,
15
        p.user_id
16
      FROM properties p
17
      LEFT JOIN assessments a ON p.id = a.property_id
18
        AND a.year = (SELECT MAX(year) FROM assessments WHERE property_id = p.id)
19
      WHERE a.status = 'preparing' OR a.status IS NULL
20
      ORDER BY p.created_at ASC
21
    `;
22

23
    const result = await pool.query(query);
5✔
24

25
    // For now, we don't have user email in the database
26
    // In production, you'd join with a users table or fetch from Clerk
27
    const properties = result.rows.map(prop => ({
4✔
28
      ...prop,
29
      status: prop.status || 'preparing',
5✔
30
      user_email: null  // TODO: Fetch from Clerk API using user_id
31
    }));
32

33
    res.json(properties);
4✔
34
  } catch (error) {
35
    console.error('Error fetching pending properties:', error);
1✔
36
    res.status(500).json({ error: 'Failed to fetch pending properties' });
1✔
37
  }
38
}
39

40
// Get all completed properties (status = 'ready')
41
export async function getCompletedProperties(req, res) {
42
  try {
4✔
43
    const query = `
4✔
44
      SELECT
45
        p.id,
46
        p.address,
47
        p.city,
48
        p.state,
49
        p.zip_code,
50
        a.status,
51
        p.created_at,
52
        p.updated_at,
53
        p.user_id
54
      FROM properties p
55
      LEFT JOIN assessments a ON p.id = a.property_id
56
        AND a.year = (SELECT MAX(year) FROM assessments WHERE property_id = p.id)
57
      WHERE a.status = 'ready'
58
      ORDER BY p.updated_at DESC
59
    `;
60

61
    const result = await pool.query(query);
4✔
62

63
    // For now, we don't have user email in the database
64
    // In production, you'd join with a users table or fetch from Clerk
65
    const properties = result.rows.map(prop => ({
3✔
66
      ...prop,
67
      user_email: null  // TODO: Fetch from Clerk API using user_id
68
    }));
69

70
    res.json(properties);
3✔
71
  } catch (error) {
72
    console.error('Error fetching completed properties:', error);
1✔
73
    res.status(500).json({ error: 'Failed to fetch completed properties' });
1✔
74
  }
75
}
76

77
// Get a single property details for admin editing
78
export async function getPropertyDetails(req, res) {
79
  try {
4✔
80
    const { id } = req.params;
4✔
81

82
    const query = `
4✔
83
      SELECT
84
        id,
85
        address,
86
        city,
87
        state,
88
        zip_code,
89
        country,
90
        lat,
91
        lng,
92
        bedrooms,
93
        bathrooms,
94
        sqft,
95
        created_at,
96
        updated_at,
97
        user_id
98
      FROM properties
99
      WHERE id = $1
100
    `;
101

102
    const result = await pool.query(query, [id]);
4✔
103

104
    if (result.rows.length === 0) {
3✔
105
      return res.status(404).json({ error: 'Property not found' });
1✔
106
    }
107

108
    const property = result.rows[0];
2✔
109

110
    // Get assessments for this property
111
    const assessmentsResult = await pool.query(
2✔
112
      `SELECT id, year, appraised_value, annual_tax, estimated_appraised_value,
113
              estimated_annual_tax, report_url, status, created_at, updated_at
114
       FROM assessments
115
       WHERE property_id = $1
116
       ORDER BY year DESC`,
117
      [id]
118
    );
119

120
    property.assessments = assessmentsResult.rows;
2✔
121

122
    // Get current year's assessment or create default
123
    const currentYear = new Date().getFullYear();
2✔
124
    const currentAssessment = assessmentsResult.rows.find(a => a.year === currentYear);
2✔
125

126
    property.currentAssessment = currentAssessment || {
2✔
127
      year: currentYear,
128
      appraised_value: null,
129
      annual_tax: null,
130
      estimated_appraised_value: null,
131
      estimated_annual_tax: null,
132
      report_url: null,
133
      status: 'preparing'
134
    };
135

136
    // Fetch user email from Clerk
137
    try {
2✔
138
      const clerkApiKey = process.env.CLERK_SECRET_KEY;
2✔
139
      if (clerkApiKey && property.user_id) {
2!
NEW
140
        const clerkResponse = await fetch(`https://api.clerk.com/v1/users/${property.user_id}`, {
×
141
          headers: {
142
            'Authorization': `Bearer ${clerkApiKey}`
143
          }
144
        });
145

NEW
146
        if (clerkResponse.ok) {
×
NEW
147
          const clerkUser = await clerkResponse.json();
×
NEW
148
          property.user_email = clerkUser.email_addresses?.[0]?.email_address || null;
×
149
        } else {
NEW
150
          property.user_email = null;
×
151
        }
152
      } else {
153
        property.user_email = null;
2✔
154
      }
155
    } catch (clerkError) {
NEW
156
      console.error('Error fetching user from Clerk:', clerkError);
×
NEW
157
      property.user_email = null;
×
158
    }
159

160
    res.json(property);
2✔
161
  } catch (error) {
162
    console.error('Error fetching property details:', error);
1✔
163
    res.status(500).json({ error: 'Failed to fetch property details' });
1✔
164
  }
165
}
166

167
// Update property details
168
export async function updatePropertyDetails(req, res) {
169
  try {
7✔
170
    const { id } = req.params;
7✔
171
    const {
172
      bedrooms,
173
      bathrooms,
174
      sqft,
175
      year,
176
      appraised_value,
177
      annual_tax,
178
      estimated_appraised_value,
179
      estimated_annual_tax,
180
      report_url,
181
      status
182
    } = req.body;
7✔
183

184
    // Update property (bedrooms, bathrooms, sqft)
185
    const propertyQuery = `
7✔
186
      UPDATE properties
187
      SET
188
        bedrooms = COALESCE($1, bedrooms),
189
        bathrooms = COALESCE($2, bathrooms),
190
        sqft = COALESCE($3, sqft),
191
        updated_at = NOW()
192
      WHERE id = $4
193
      RETURNING *
194
    `;
195

196
    const propertyResult = await pool.query(propertyQuery, [
7✔
197
      bedrooms !== undefined ? bedrooms : null,
7✔
198
      bathrooms !== undefined ? bathrooms : null,
7✔
199
      sqft !== undefined ? sqft : null,
7✔
200
      id
201
    ]);
202

203
    if (propertyResult.rows.length === 0) {
6✔
204
      return res.status(404).json({ error: 'Property not found' });
1✔
205
    }
206

207
    // Update or create assessment for the specified year (or current year if not specified)
208
    const assessmentYear = year || new Date().getFullYear();
5✔
209

210
    const assessmentQuery = `
5✔
211
      INSERT INTO assessments (id, property_id, year, appraised_value, annual_tax,
212
                               estimated_appraised_value, estimated_annual_tax, report_url,
213
                               status, created_at, updated_at)
214
      VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, NOW(), NOW())
215
      ON CONFLICT (property_id, year)
216
      DO UPDATE SET
217
        appraised_value = COALESCE($4, assessments.appraised_value),
218
        annual_tax = COALESCE($5, assessments.annual_tax),
219
        estimated_appraised_value = COALESCE($6, assessments.estimated_appraised_value),
220
        estimated_annual_tax = COALESCE($7, assessments.estimated_annual_tax),
221
        report_url = COALESCE($8, assessments.report_url),
222
        status = COALESCE($9, assessments.status),
223
        updated_at = NOW()
224
      RETURNING *
225
    `;
226

227
    const assessmentId = `assess_${id}_${assessmentYear}`;
5✔
228
    const assessmentResult = await pool.query(assessmentQuery, [
5✔
229
      assessmentId,
230
      id,
231
      assessmentYear,
232
      appraised_value !== undefined ? appraised_value : null,
5✔
233
      annual_tax !== undefined ? annual_tax : null,
5✔
234
      estimated_appraised_value !== undefined ? estimated_appraised_value : null,
5✔
235
      estimated_annual_tax !== undefined ? estimated_annual_tax : null,
5✔
236
      report_url !== undefined ? report_url : null,
5✔
237
      status !== undefined ? status : null
5✔
238
    ]);
239

240
    // Return combined result
241
    const response = {
5✔
242
      ...propertyResult.rows[0],
243
      currentAssessment: assessmentResult.rows[0]
244
    };
245

246
    res.json(response);
5✔
247
  } catch (error) {
248
    console.error('Error updating property details:', error);
1✔
249
    res.status(500).json({ error: 'Failed to update property details' });
1✔
250
  }
251
}
252

253
// Mark property as ready (legacy function, kept for backward compatibility)
254
export async function markPropertyAsReady(req, res) {
255
  try {
4✔
256
    const { id } = req.params;
4✔
257

258
    const query = `
4✔
259
      UPDATE properties
260
      SET status = 'ready', updated_at = NOW()
261
      WHERE id = $1
262
      RETURNING *
263
    `;
264

265
    const result = await pool.query(query, [id]);
4✔
266

267
    if (result.rows.length === 0) {
3✔
268
      return res.status(404).json({ error: 'Property not found' });
1✔
269
    }
270

271
    res.json(result.rows[0]);
2✔
272
  } catch (error) {
273
    console.error('Error marking property as ready:', error);
1✔
274
    res.status(500).json({ error: 'Failed to mark property as ready' });
1✔
275
  }
276
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc