Compare commits
8 Commits
master
...
358b0328e7
| Author | SHA1 | Date | |
|---|---|---|---|
| 358b0328e7 | |||
| 2b29e19306 | |||
| 3e6eb59251 | |||
| 0457271b57 | |||
| 4337f7a381 | |||
| f49d107061 | |||
| 998e9a8ab8 | |||
| 28d7d41b25 |
5
.gitignore
vendored
5
.gitignore
vendored
@@ -220,5 +220,8 @@ local/
|
||||
*.swp
|
||||
|
||||
# Local file uploads
|
||||
src/TrueCV.Web/uploads/
|
||||
src/RealCV.Web/uploads/
|
||||
logs/
|
||||
|
||||
# Screenshots
|
||||
screenshots/
|
||||
|
||||
@@ -1,515 +0,0 @@
|
||||
# TrueCV UK APIs & Integration Resources
|
||||
|
||||
**Last Updated:** January 2026
|
||||
**Purpose:** Practical guide for obtaining API access and integration details
|
||||
|
||||
---
|
||||
|
||||
## 1. HEDD (Higher Education Degree Datacheck)
|
||||
|
||||
### Overview
|
||||
- **Service:** UK degree verification against 140+ university records
|
||||
- **Coverage:** All UK Russell Group + most other UK universities
|
||||
- **Request Type:** Real-time matching + manual university verification (10 days)
|
||||
|
||||
### Access & Integration
|
||||
|
||||
#### Contact Information
|
||||
- **Website:** https://hedd.ac.uk/
|
||||
- **For API/Integration Inquiries:**
|
||||
- Contact: partnerships@hedd.ac.uk
|
||||
- Business Development: [HEDD website contact form](https://hedd.ac.uk/)
|
||||
- Alternative: Prospects Limited (maintains HEDD)
|
||||
|
||||
#### Integration Methods
|
||||
|
||||
**Option A: REST API (Preferred - Direct)**
|
||||
- **Status:** Available for registered partners
|
||||
- **Endpoint Base:** `https://api.hedd.ac.uk/v2/`
|
||||
- **Authentication:** API Key (basic auth)
|
||||
- **Rate Limits:** Typically 500 requests/day (negotiable)
|
||||
- **Response Time:** <2 seconds for exact matches
|
||||
- **Cost:** £1-5 per verification (pass-through to customers)
|
||||
|
||||
**Option B: Web Portal Integration (Fallback)**
|
||||
- **Status:** Available immediately to registered employers
|
||||
- **Registration:** https://hedd.ac.uk/employers
|
||||
- **Process:** Embed form or redirect to HEDD portal
|
||||
- **Response:** Email notification when manual review completes
|
||||
- **Cost:** Same as API (£1-5 per verification)
|
||||
- **Implementation:** 3-5 days (iframe/redirect pattern)
|
||||
|
||||
#### Required Information for Registration
|
||||
- Company/organization name
|
||||
- Principal contact person
|
||||
- Use case (CV verification for recruitment)
|
||||
- Expected volume (verifications/month)
|
||||
- Data handling procedure (consent workflow)
|
||||
- GDPR/data protection process
|
||||
- Whether requiring API vs. web portal access
|
||||
|
||||
#### Timeline for Access
|
||||
- **Application review:** 5-10 business days
|
||||
- **Approval + credential issue:** +3-5 business days
|
||||
- **API testing:** +2-3 business days
|
||||
- **Total:** 10-20 days (best case)
|
||||
|
||||
#### API Documentation
|
||||
- **Base URL:** https://api.hedd.ac.uk/v2/
|
||||
- **Key Endpoints:**
|
||||
- `POST /api/verify/degree` - Submit verification request
|
||||
- `GET /api/verify/status/{referenceId}` - Check manual review status
|
||||
- `GET /api/institutions` - List participating universities
|
||||
- `POST /api/batch` - Batch verification (if available)
|
||||
|
||||
---
|
||||
|
||||
## 2. GMC Register (General Medical Council)
|
||||
|
||||
### Overview
|
||||
- **Service:** UK medical practitioner registration and verification
|
||||
- **Coverage:** ~250K registered doctors in UK
|
||||
- **Searchable:** Public website at https://www.gmc-uk.org/
|
||||
|
||||
### Access & Integration
|
||||
|
||||
#### Contact Information
|
||||
- **Main Website:** https://www.gmc-uk.org/
|
||||
- **Registration Search:** https://www.gmc-uk.org/registration-and-licensing/the-medical-register
|
||||
- **For API/Integration:**
|
||||
- Digital Services: digital@gmc-uk.org
|
||||
- Developer Info: [Check developer portal/API docs]
|
||||
- Business Development: partnerships@gmc-uk.org
|
||||
|
||||
#### Integration Methods
|
||||
|
||||
**Option A: Official API (Recommended)**
|
||||
- **Status:** Available for verification services
|
||||
- **Endpoint Base:** Likely `https://www.gmc-uk.org/api/v1/` or similar
|
||||
- **Authentication:** OAuth2 or API Key
|
||||
- **Rate Limits:** TBD with GMC
|
||||
- **Response Time:** <1 second
|
||||
- **Cost:** Free or nominal fee (TBD)
|
||||
|
||||
**Option B: Web Scraping (Immediate Alternative)**
|
||||
- **Status:** Legal for aggregation/verification purposes
|
||||
- **Target:** https://www.gmc-uk.org/
|
||||
- **Method:** BeautifulSharp/Selenium for search results
|
||||
- **Implementation:** 5-7 days (C# scraper)
|
||||
- **Risk:** Minor - GMC unlikely to block verification use case
|
||||
- **Maintenance:** Monitor for website structure changes quarterly
|
||||
|
||||
#### Required Information for API Request
|
||||
- Organization name + registration number
|
||||
- Intended use case (CV verification)
|
||||
- Expected request volume
|
||||
- Data protection/GDPR compliance
|
||||
- Integration timeline/urgency
|
||||
|
||||
#### API Documentation (if available)
|
||||
- Likely endpoints:
|
||||
- `GET /api/doctors/search?name={name}` - Search by name
|
||||
- `GET /api/doctors/{gmcNumber}` - Lookup by GMC number
|
||||
- `GET /api/doctors/status?name={name}&specialty={specialty}` - Verify status
|
||||
|
||||
#### Timeline for Access
|
||||
- **API Request → Review:** 2-4 weeks
|
||||
- **If rejected:** Fallback to web scraper (5-7 days dev)
|
||||
|
||||
---
|
||||
|
||||
## 3. NMC Register (Nursing and Midwifery Council)
|
||||
|
||||
### Overview
|
||||
- **Service:** UK nurse/midwife registration
|
||||
- **Coverage:** ~700K registered nurses, midwives, nursing associates
|
||||
- **Searchable:** Public website at https://www.nmc.org.uk/
|
||||
|
||||
### Access & Integration
|
||||
|
||||
#### Contact Information
|
||||
- **Main Website:** https://www.nmc.org.uk/
|
||||
- **Register Search:** https://www.nmc.org.uk/registration/search-the-register/
|
||||
- **For API/Integration:**
|
||||
- Digital Services: [Check website for tech contact]
|
||||
- Developer Relations: [Likely on website or contact form]
|
||||
- Main Contact: www.nmc.org.uk/contact
|
||||
|
||||
#### Integration Methods
|
||||
|
||||
**Option A: Official API**
|
||||
- **Status:** Available for verification partners
|
||||
- **Endpoint Base:** Likely `https://api.nmc.org.uk/` or similar
|
||||
- **Authentication:** OAuth2 or API Key
|
||||
- **Cost:** Free or nominal
|
||||
- **Implementation:** Same pattern as GMC
|
||||
|
||||
**Option B: Web Scraping**
|
||||
- **Status:** Legal for verification
|
||||
- **Target:** https://www.nmc.org.uk/
|
||||
- **Implementation:** 5-7 days (reusable pattern from GMC scraper)
|
||||
- **Risk:** Low
|
||||
|
||||
#### Timeline
|
||||
- **Same as GMC:** 2-4 weeks (API) or 5-7 days (scraper fallback)
|
||||
|
||||
---
|
||||
|
||||
## 4. Companies House API (Already Integrated)
|
||||
|
||||
### Overview
|
||||
- **Service:** UK company registration and officer records
|
||||
- **Status:** ✅ Already integrated in TrueCV
|
||||
- **Coverage:** 3.4M registered UK companies
|
||||
|
||||
### Enhancement Opportunities
|
||||
|
||||
#### Existing Implementation
|
||||
- See: `/src/TrueCV.Infrastructure/ExternalApis/CompaniesHouseClient.cs`
|
||||
- Current: Company search + basic data lookup
|
||||
- Rate Limit: 500 requests/hour (generous)
|
||||
|
||||
#### New Endpoints to Utilize
|
||||
|
||||
**Officers/Directors API:**
|
||||
- **Endpoint:** `/company/{companyNumber}/officers`
|
||||
- **Returns:** List of directors, secretaries, appointments
|
||||
- **Use Case:** Verify director claims against employment history
|
||||
- **Implementation:** Already drafted in Phase 1 technical doc
|
||||
|
||||
**Disqualifications API:**
|
||||
- **Endpoint:** `/disqualifications` (if available)
|
||||
- **Returns:** Directors banned from serving
|
||||
- **Use Case:** Flag disqualified director claims
|
||||
- **Implementation:** 2-3 days
|
||||
|
||||
**Charges/Mortgages API:**
|
||||
- **Endpoint:** `/company/{companyNumber}/charges`
|
||||
- **Use Case:** Assess company financial stability
|
||||
- **Implementation:** Optional enhancement
|
||||
|
||||
#### Documentation
|
||||
- **Official Docs:** https://developer.companieshouse.gov.uk/
|
||||
- **Key Endpoints Already Used:**
|
||||
- `/search/companies?q={query}`
|
||||
- `/company/{companyNumber}`
|
||||
- **New Endpoints:**
|
||||
- `/company/{companyNumber}/officers`
|
||||
- `/company/{companyNumber}/disqualifications`
|
||||
|
||||
---
|
||||
|
||||
## 5. GOV.UK Regulated Professions Register
|
||||
|
||||
### Overview
|
||||
- **Service:** Directory of 140+ regulated professions in UK
|
||||
- **URL:** https://www.regulated-professions.service.gov.uk/
|
||||
- **Use Case:** Cross-check CV claims against official regulator list
|
||||
|
||||
### Access & Integration
|
||||
|
||||
#### Integration Type
|
||||
- **API:** Public REST API available
|
||||
- **Documentation:** https://www.regulated-professions.service.gov.uk/
|
||||
- **Authentication:** None (public data)
|
||||
- **Rate Limits:** Minimal/none
|
||||
- **Cost:** Free
|
||||
|
||||
#### Key Endpoints
|
||||
- `GET /professions` - List all regulated professions
|
||||
- `GET /professions/search?q={query}` - Search by profession name
|
||||
- `GET /professions/{id}/regulators` - Get regulator info
|
||||
|
||||
#### Implementation
|
||||
- **Effort:** 2-3 days (simple enrichment layer)
|
||||
- **Purpose:** When CV claims regulated profession, validate regulator exists
|
||||
- **Example:** CV says "Chartered Accountant (ICAEW)" → Verify ICAEW in register
|
||||
|
||||
---
|
||||
|
||||
## 6. ICAEW Register (Accountants)
|
||||
|
||||
### Overview
|
||||
- **Service:** Institute of Chartered Accountants in England & Wales
|
||||
- **Coverage:** ~180K members
|
||||
- **Website:** https://www.icaew.com/
|
||||
|
||||
### Access & Integration
|
||||
|
||||
#### Contact Information
|
||||
- **Member Search:** https://www.icaew.com/find-a-member
|
||||
- **For API/Integration:**
|
||||
- Technical Contact: [Check website]
|
||||
- Business Development: [Check website contact]
|
||||
- Email: partnerships@icaew.com
|
||||
|
||||
#### Integration Methods
|
||||
|
||||
**Option A: API (Recommended)**
|
||||
- **Status:** Check if available for third-party verification
|
||||
- **Implementation:** 2-3 weeks (likely similar to GMC/NMC pattern)
|
||||
|
||||
**Option B: Web Scraping**
|
||||
- **Status:** Legal for verification purposes
|
||||
- **Target:** https://www.icaew.com/find-a-member
|
||||
- **Implementation:** 7-10 days
|
||||
|
||||
#### Data Points to Verify
|
||||
- Member status (Active/Retired/Lapsed)
|
||||
- Membership type (ACA/FCA/AAIA/etc.)
|
||||
- Regulated areas (audit, insolvency, etc.)
|
||||
|
||||
---
|
||||
|
||||
## 7. SRA Register (Solicitors)
|
||||
|
||||
### Overview
|
||||
- **Service:** Solicitors Regulation Authority
|
||||
- **Coverage:** ~170K solicitors in UK
|
||||
- **Website:** https://www.sra.org.uk/
|
||||
|
||||
### Access & Integration
|
||||
|
||||
#### Contact Information
|
||||
- **Solicitor Search:** https://www.sra.org.uk/solicitors/
|
||||
- **For API/Integration:**
|
||||
- Technical Services: [Check website]
|
||||
- Business Partnerships: [Check website]
|
||||
- Email: Try via website contact form
|
||||
|
||||
#### Integration Methods
|
||||
- **Same pattern as ICAEW (API or scraper)**
|
||||
- **Implementation:** 7-10 days total
|
||||
- **Data Points:** Solicitor status, specializations, practice areas
|
||||
|
||||
---
|
||||
|
||||
## 8. IET Register (Engineers)
|
||||
|
||||
### Overview
|
||||
- **Service:** Institution of Engineering and Technology
|
||||
- **Coverage:** ~150K members
|
||||
- **Website:** https://www.theiet.org/
|
||||
|
||||
### Access & Integration
|
||||
|
||||
#### Similar to ICAEW/SRA
|
||||
- **Contact:** partnerships@theiet.org
|
||||
- **Member Search:** Available on website
|
||||
- **API Status:** Check with IET directly
|
||||
|
||||
---
|
||||
|
||||
## 9. HCPC Register (Healthcare Professionals)
|
||||
|
||||
### Overview
|
||||
- **Service:** Health and Care Professions Council
|
||||
- **Coverage:** 15 regulated professions (physios, psychologists, paramedics, etc.)
|
||||
- **Website:** https://www.hcpc-uk.org/
|
||||
|
||||
### Access & Integration
|
||||
- **Register Search:** https://www.hcpc-uk.org/registration/the-register/
|
||||
- **For API:** Contact digital@hcpc-uk.org
|
||||
- **Implementation:** 2-3 weeks (if API available) or 7-10 days (scraper)
|
||||
|
||||
---
|
||||
|
||||
## 10. DBS Integration (Partnership Required)
|
||||
|
||||
### Overview
|
||||
- **Service:** Disclosure and Barring Service checks
|
||||
- **No Direct API:** Accessed through pre-employment screening vendors
|
||||
- **Vendors offering DBS APIs:**
|
||||
- Verifile
|
||||
- DDC (Due Diligence Checking)
|
||||
- Security Watchdog
|
||||
- uCheck
|
||||
- Certn
|
||||
|
||||
### Recommended Vendor for TrueCV Integration
|
||||
|
||||
**Verifile** (Suggested)
|
||||
- **Website:** https://www.verifile.io/
|
||||
- **Contact:** [Check website]
|
||||
- **API Type:** REST-based
|
||||
- **Cost Structure:** £20-50 per DBS check (pass-through)
|
||||
- **Integration:** 6-8 weeks (includes compliance setup)
|
||||
|
||||
### Alternative Vendors
|
||||
- **DDC:** https://www.ddc.uk.net/
|
||||
- **Security Watchdog:** https://www.securitywatchdog.org.uk/
|
||||
- **uCheck:** https://www.ucheck.co.uk/
|
||||
|
||||
### Implementation Approach
|
||||
1. Contact 2-3 vendors for partnership discussion
|
||||
2. Negotiate revenue share (typically 20-30% for platform)
|
||||
3. Integrate DBS check submission API
|
||||
4. Build compliance/audit trail layer
|
||||
5. White-label DBS reports in TrueCV UI
|
||||
|
||||
### Timeline
|
||||
- **Vendor selection:** 1-2 weeks
|
||||
- **Agreement negotiation:** 2-4 weeks
|
||||
- **Technical integration:** 6-8 weeks
|
||||
- **Compliance approval:** 2-4 weeks
|
||||
- **Total:** 12-18 weeks (Q3 timeline realistic)
|
||||
|
||||
---
|
||||
|
||||
## 11. HMRC Payroll Verification (Restricted Access)
|
||||
|
||||
### Overview
|
||||
- **Service:** Real-time employment verification via HMRC
|
||||
- **Access:** Restricted to pre-employment screening vendors with accreditation
|
||||
- **Use Case:** Authoritative employment history + dates + salary bands
|
||||
|
||||
### Implementation Approach
|
||||
|
||||
**NOT Direct API Access** - Must partner with accredited vendor
|
||||
|
||||
#### Recommended Path
|
||||
1. **Contact accredited vendors:** Verifile, DDC, or similar
|
||||
2. **Explain use case:** CV verification platform
|
||||
3. **Request sub-licensing:** Access to their HMRC integration
|
||||
4. **Build wrapper:** TrueCV UI calls vendor API
|
||||
|
||||
#### Vendors with HMRC Access
|
||||
- Verifile (https://www.verifile.io/)
|
||||
- DDC (https://www.ddc.uk.net/)
|
||||
- Digital Marketplace vendors (check list)
|
||||
|
||||
#### Timeline
|
||||
- **Vendor discussion:** 2-4 weeks
|
||||
- **Partnership agreement:** 4-6 weeks
|
||||
- **Technical integration:** 4-6 weeks
|
||||
- **Total:** 10-16 weeks (Q3 2026)
|
||||
|
||||
#### Cost Model
|
||||
- Likely: £0.50-2 per verification (wholesale rate)
|
||||
- Pass-through cost to customers: £2-5
|
||||
|
||||
---
|
||||
|
||||
## Implementation Prioritization for Phase 1
|
||||
|
||||
| Component | Primary API | Fallback | Effort | Start | Complete |
|
||||
|---|---|---|---|---|---|
|
||||
| **HEDD** | ✅ API | Web portal | 3 weeks | Week 1 | Week 3 |
|
||||
| **GMC** | 🔄 API TBD | Scraper | 1 week | Week 2 | Week 3 |
|
||||
| **NMC** | 🔄 API TBD | Scraper | 1 week | Week 2 | Week 3 |
|
||||
| **Companies House** | ✅ API exist | N/A | 2 weeks | Week 1 | Week 3 |
|
||||
| **GOV.UK Registry** | ✅ API public | N/A | 3 days | Week 2 | Week 2 |
|
||||
| **Timeline Enhancement** | N/A | Internal | 1 week | Week 1 | Week 1 |
|
||||
|
||||
---
|
||||
|
||||
## Action Items for Product Manager
|
||||
|
||||
### This Week
|
||||
1. **Email HEDD:** partnerships@hedd.ac.uk with:
|
||||
- Company info (TrueCV)
|
||||
- Use case (CV verification for UK recruiters)
|
||||
- Expected volume (start with 100/month)
|
||||
- Request: API access or partnership discussion
|
||||
|
||||
2. **Email GMC:** digital@gmc-uk.org with similar inquiry
|
||||
|
||||
3. **Email NMC:** [Check website for technical contact]
|
||||
|
||||
4. **Review Companies House API Docs:** https://developer.companieshouse.gov.uk/
|
||||
|
||||
### Next Week
|
||||
1. **Follow up if no response:** Contact alternative channels (partnerships@, main contact)
|
||||
2. **Prepare scraper approach:** If APIs not available, start scraper development anyway
|
||||
3. **Create test accounts:** Register on HEDD, GMC, NMC websites as backup
|
||||
4. **Identify beta partners:** Contact recruitment agencies for testing
|
||||
|
||||
### Timeline Expectations
|
||||
- **HEDD API Response:** 2-4 weeks
|
||||
- **GMC API Response:** 2-4 weeks (or fallback to scraper)
|
||||
- **NMC API Response:** 2-4 weeks (or fallback to scraper)
|
||||
- **If APIs unavailable:** Scraper approach = 3-4 days per service
|
||||
- **Companies House:** Already have access; can start immediately
|
||||
|
||||
---
|
||||
|
||||
## Compliance & Data Protection Checklist
|
||||
|
||||
For each API integration, ensure:
|
||||
- [ ] Terms of Service reviewed (especially data retention/use restrictions)
|
||||
- [ ] GDPR data processing agreement in place
|
||||
- [ ] Candidate consent workflow implemented
|
||||
- [ ] Data retention policy documented
|
||||
- [ ] Audit logging enabled
|
||||
- [ ] Data deletion procedures defined
|
||||
- [ ] Third-party processing agreement signed (where applicable)
|
||||
- [ ] Privacy notice updated on website
|
||||
|
||||
---
|
||||
|
||||
## References & Documentation
|
||||
|
||||
### Official API Documentation Links
|
||||
- **Companies House:** https://developer.companieshouse.gov.uk/
|
||||
- **HEDD:** https://hedd.ac.uk/ (contact for API docs)
|
||||
- **GMC:** https://www.gmc-uk.org/ (check for developer resources)
|
||||
- **NMC:** https://www.nmc.org.uk/ (check for developer resources)
|
||||
- **GOV.UK Professions:** https://www.regulated-professions.service.gov.uk/
|
||||
- **DBS Vendors:** Contact directly
|
||||
|
||||
### Useful Resources
|
||||
- [UK Pre-Employment Screening Industry Overview](https://www.verifyed.io/)
|
||||
- [HEDD Employers Toolkit](https://hedd.ac.uk/employers)
|
||||
- [UK Data Protection Act 2018](https://www.legislation.gov.uk/ukpga/2018/12/contents/enacted)
|
||||
- [GDPR Requirements for HR](https://ico.org.uk/)
|
||||
|
||||
---
|
||||
|
||||
## Contact Template for API Requests
|
||||
|
||||
```
|
||||
Subject: API Integration Request - TrueCV Recruitment Verification Platform
|
||||
|
||||
Dear [Service] Team,
|
||||
|
||||
We are developing TrueCV, a UK-focused CV verification platform for recruitment agencies and corporate HR departments. As part of our Phase 1 launch (Q1 2026), we would like to integrate with [Service Name] to verify [candidate credentials] in real-time during the hiring process.
|
||||
|
||||
Use Case:
|
||||
- Candidates upload CV during job application
|
||||
- TrueCV extracts education/qualification claims
|
||||
- Real-time verification against [Service] records
|
||||
- Fraud flags generated for recruiter review
|
||||
|
||||
Integration Preference:
|
||||
- REST API integration (preferred)
|
||||
- Web portal integration (acceptable)
|
||||
|
||||
Anticipated Volume:
|
||||
- Initial: 100-500 verifications/month
|
||||
- Scale: 5,000+ verifications/month (Year 2)
|
||||
|
||||
Questions:
|
||||
1. Is API access available for third-party verification services?
|
||||
2. What is the application timeline?
|
||||
3. Are there rate limits or volume commitments?
|
||||
4. Is there a cost per verification or licensing fee?
|
||||
5. What data retention policies apply?
|
||||
|
||||
We're committed to compliance and will execute necessary data processing agreements.
|
||||
|
||||
Please advise next steps.
|
||||
|
||||
Best regards,
|
||||
[Your Name]
|
||||
TrueCV
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
1. **Start with HEDD:** Highest ROI; contact this week
|
||||
2. **Parallel track GMC/NMC:** Prepare scraper approach as backup
|
||||
3. **Companies House:** Begin director verification enhancement immediately (API exists)
|
||||
4. **Timeline:** Full Phase 1 integration possible within 8 weeks
|
||||
|
||||
@@ -1,476 +0,0 @@
|
||||
================================================================================
|
||||
TRUECV UK MARKET STRATEGY - COMPLETE DELIVERY PACKAGE
|
||||
================================================================================
|
||||
|
||||
Project: Rethinking TrueCV Feature Priorities with UK-Only Focus
|
||||
Date Delivered: January 20, 2026
|
||||
Total Documents: 8 comprehensive strategy guides
|
||||
Total Content: ~200 pages
|
||||
Estimated Read Time: 2-4 hours (depending on role)
|
||||
|
||||
================================================================================
|
||||
DELIVERABLES
|
||||
================================================================================
|
||||
|
||||
FILE 1: QUICK_REFERENCE.md (3-4 pages)
|
||||
- Purpose: Desk reference card for quick lookups
|
||||
- Format: Visual tables, bullet points, one-liners
|
||||
- Contains: Market opportunity, competitive advantage, timeline, pricing, metrics
|
||||
- Best For: All audiences - print and keep at desk
|
||||
|
||||
FILE 2: EXECUTIVE_SUMMARY.md (5-6 pages)
|
||||
- Purpose: Executive brief for decision-making
|
||||
- Format: Business narrative with financial projections
|
||||
- Contains: Market problem, solution, competitive advantage, financials, 30-day plan
|
||||
- Best For: Executives, investors, decision-makers
|
||||
|
||||
FILE 3: UK_FEATURE_PRIORITIZATION.md (25-30 pages)
|
||||
- Purpose: Detailed feature prioritization and analysis
|
||||
- Format: Tables, matrices, ranked lists, implementation examples
|
||||
- Contains: 8 UK APIs analyzed, features ranked by impact×feasibility, 3-phase roadmap
|
||||
- Best For: Product managers, engineering leads
|
||||
|
||||
FILE 4: PHASE1_TECHNICAL_IMPLEMENTATION.md (50-60 pages)
|
||||
- Purpose: Complete technical specifications for Phase 1 (8-week) delivery
|
||||
- Format: Architecture diagrams, production-ready C# code, configuration guides
|
||||
- Contains: 4 features with complete implementation details, testing strategy, deployment checklist
|
||||
- Best For: Backend engineers, QA engineers
|
||||
|
||||
FILE 5: UK_MARKET_STRATEGY.md (40-50 pages)
|
||||
- Purpose: Comprehensive market and go-to-market strategy
|
||||
- Format: Market analysis, competitive landscape, GTM strategy, financials
|
||||
- Contains: Market sizing, competitive analysis, 3-phase product strategy, GTM channels, unit economics
|
||||
- Best For: Product team, marketing, sales, leadership
|
||||
|
||||
FILE 6: API_RESOURCES_AND_CONTACTS.md (20-25 pages)
|
||||
- Purpose: Practical guide to accessing UK APIs and vendor partnerships
|
||||
- Format: Reference guide, contact information, implementation methods
|
||||
- Contains: 11 API integration guides, contact details, email templates, compliance checklist
|
||||
- Best For: Engineering + product during implementation phase
|
||||
|
||||
FILE 7: README_UK_STRATEGY.md (8-10 pages)
|
||||
- Purpose: Navigation guide and orientation document
|
||||
- Format: Document hierarchy, role-based reading paths, cross-references
|
||||
- Contains: Quick navigation, decision framework, reading order recommendations
|
||||
- Best For: Orientation and finding specific information
|
||||
|
||||
FILE 8: INDEX.md (10-12 pages)
|
||||
- Purpose: Complete document index and reference guide
|
||||
- Format: Document inventory, cross-references, reading guides by role
|
||||
- Contains: What's in each document, how to find information, version control
|
||||
- Best For: Understanding what information exists where
|
||||
|
||||
================================================================================
|
||||
KEY FINDINGS & RECOMMENDATIONS
|
||||
================================================================================
|
||||
|
||||
MARKET OPPORTUNITY:
|
||||
✓ £4.2B annual cost of CV fraud to UK employers
|
||||
✓ 1 in 5 UK candidates falsify university degrees
|
||||
✓ 24% of screened CVs fail verification
|
||||
✓ £3.3M serviceable market for TrueCV
|
||||
✓ No existing competitor offers integrated UK CV verification
|
||||
|
||||
COMPETITIVE ADVANTAGE:
|
||||
✓ Only platform integrating HEDD degree verification (no competitors do)
|
||||
✓ Only tool targeting healthcare recruiting niche (GMC/NMC registers)
|
||||
✓ Only solution verifying director claims vs. Companies House
|
||||
✓ Only platform detecting timeline fraud across education-employment boundary
|
||||
✓ 6-12 month first-mover advantage window
|
||||
|
||||
RECOMMENDED STRATEGY:
|
||||
✓ PROCEED with Phase 1 implementation immediately
|
||||
✓ Launch 4 features in 8 weeks (Q1 2026)
|
||||
✓ Target healthcare recruiting niche first (GMC/NMC)
|
||||
✓ Expand to professional bodies in Q2 (ICAEW, SRA)
|
||||
✓ Add compliance tier (DBS, HMRC) in Q3
|
||||
|
||||
FINANCIAL PROJECTIONS:
|
||||
✓ Year 1 Revenue: £113K-226K (conservative to growth)
|
||||
✓ Break-even: 24-30 customers (achievable by month 6-7)
|
||||
✓ Customer Acquisition Cost: £150-300
|
||||
✓ Average Revenue Per User: £60-120/month
|
||||
✓ Gross Margin: 75-80% (healthy SaaS model)
|
||||
|
||||
CRITICAL PATH:
|
||||
1. Email HEDD requesting API access (this week)
|
||||
2. Email GMC/NMC requesting verification APIs (this week)
|
||||
3. Allocate 2 engineers full-time for 8 weeks (immediate)
|
||||
4. Recruit 3-5 beta partner recruitment agencies (week 2)
|
||||
5. Begin development (week 2)
|
||||
6. Public launch (week 8)
|
||||
|
||||
================================================================================
|
||||
PHASE 1 FEATURE PRIORITIES (Q1 2026 - 8 Weeks)
|
||||
================================================================================
|
||||
|
||||
RANK FEATURE IMPACT EFFORT TIMELINE
|
||||
─────────────────────────────────────────────────────────────────────
|
||||
1. HEDD Degree Verification 9.5/10 ★★★ Weeks 1-3
|
||||
└─ Real-time + manual review tracking
|
||||
|
||||
2. Enhanced Timeline Analysis 7.0/10 ★☆☆ Weeks 1-2
|
||||
└─ Education-employment sequencing
|
||||
|
||||
3. Healthcare Registers (GMC/NMC) 6.5/10 ★☆☆ Weeks 2-3
|
||||
└─ Doctor/nurse registration verification
|
||||
|
||||
4. Companies House Director 7.5/10 ★★☆ Weeks 2-4
|
||||
Verification
|
||||
└─ Self-employment claim validation
|
||||
|
||||
APIs INTEGRATED:
|
||||
✓ HEDD (degree verification, 140+ universities)
|
||||
✓ GMC (doctor registration, 250K practitioners)
|
||||
✓ NMC (nurse registration, 700K practitioners)
|
||||
✓ Companies House Directors (existing API enhancement)
|
||||
✓ GOV.UK Regulated Professions (enrichment layer)
|
||||
|
||||
EXPECTED OUTCOMES:
|
||||
✓ 500+ signups in first month
|
||||
✓ 10%+ weekly active check rate
|
||||
✓ 85%+ feature satisfaction
|
||||
✓ 90%+ accuracy on fraud detection
|
||||
|
||||
================================================================================
|
||||
COMPETITIVE LANDSCAPE ANALYSIS
|
||||
================================================================================
|
||||
|
||||
COMPETITOR FEATURES OFFERED TrueCV ADVANTAGE
|
||||
─────────────────────────────────────────────────────────────
|
||||
Workable ATS + basic screening HEDD integration (exclusive)
|
||||
Deel Global hiring + screening UK-specific stack
|
||||
Checkr Background checks + DBS Timeline fraud detection
|
||||
Verifile Pre-employment screening Healthcare niche dominance
|
||||
Veriff Identity verification CV-focused approach
|
||||
|
||||
MARKET GAP:
|
||||
No existing competitor integrates:
|
||||
- HEDD degree verification
|
||||
- GMC/NMC healthcare registers
|
||||
- Timeline fraud detection
|
||||
- Companies House director verification
|
||||
→ TrueCV is only player filling this gap
|
||||
|
||||
MOAT BUILDING:
|
||||
- Deep integrations difficult to replicate (6+ months each)
|
||||
- Network effects as data accumulates
|
||||
- Regulatory compliance/audit trail = switching costs
|
||||
- Vertical dominance in healthcare (first-mover)
|
||||
|
||||
================================================================================
|
||||
FRAUD DETECTION COVERAGE
|
||||
================================================================================
|
||||
|
||||
FRAUD TYPE DETECTION RATE PHASE
|
||||
────────────────────────────────────────────────────────────
|
||||
Fake/False Degrees 90%+ Phase 1
|
||||
Employment Date Falsification 80%+ Phase 1
|
||||
Directorship False Claims 95%+ Phase 1
|
||||
Job Title Inflation Partial Phase 1
|
||||
Exaggerated Qualifications 85%+ Phase 2
|
||||
Professional Certification Fraud 95%+ Phase 2
|
||||
Timeline Gaps/Overlaps 85%+ Phase 1
|
||||
|
||||
PHASE 1 COVERAGE: ~80% of common fraud patterns
|
||||
|
||||
================================================================================
|
||||
GO-TO-MARKET STRATEGY
|
||||
================================================================================
|
||||
|
||||
PRIMARY CHANNELS:
|
||||
1. Direct Sales (target: agency owners, HR directors)
|
||||
Expected conversion: 5-8%
|
||||
Sales cycle: 2-4 weeks
|
||||
|
||||
2. Partnerships (ATS integrations, background check white-label)
|
||||
Expected impact: +30% user acquisition
|
||||
|
||||
3. Content & SEO (blog, case studies, webinars)
|
||||
Expected impact: +20% organic users
|
||||
|
||||
4. Vertical Specialists (healthcare, finance, legal recruiters)
|
||||
Expected impact: +25% high-value customers
|
||||
|
||||
CUSTOMER TIERS:
|
||||
┌─────────────────────────────────────────────────────┐
|
||||
│ TIER PRICE/MO TARGET CUSTOMER │
|
||||
│ ─────────────────────────────────────────────────── │
|
||||
│ Free £0 Solo recruiters │
|
||||
│ Professional £49/month Small agencies (50-200) │
|
||||
│ Enterprise £199/month Large orgs (200+) │
|
||||
│ API/Platform £1,000/mo Integration partners │
|
||||
└─────────────────────────────────────────────────────┘
|
||||
|
||||
PRICING UNIT ECONOMICS:
|
||||
- Customer Acquisition Cost (CAC): £150-300
|
||||
- Average Revenue Per User (ARPU): £60-120/month
|
||||
- Payback Period: 2-4 months
|
||||
- LTV:CAC Ratio: 4:1+ (healthy SaaS benchmark)
|
||||
|
||||
================================================================================
|
||||
TEAM REQUIREMENTS
|
||||
================================================================================
|
||||
|
||||
PHASE 1 (Q1 2026):
|
||||
✓ Backend Engineer (Lead): Full-time 8 weeks - HEDD integration
|
||||
✓ Backend Engineer (Secondary): Full-time 8 weeks - Healthcare + Timeline
|
||||
✓ QA Engineer: Part-time (weeks 2-3)
|
||||
✓ Product Manager: Full-time (coordination)
|
||||
✓ Marketing Lead: Part-time 50% (content & outreach)
|
||||
|
||||
PHASE 2 (Q2 2026) ADD:
|
||||
✓ Full-Stack Engineer (vertical expansion)
|
||||
✓ Sales/BD Lead (partnership development)
|
||||
|
||||
PHASE 3 (Q3 2026) ADD:
|
||||
✓ Customer Success Manager
|
||||
✓ Data Analyst (metrics/LTV)
|
||||
|
||||
ESTIMATED BUDGET:
|
||||
Phase 1: £40-50K (8 weeks, 2 engineers + support)
|
||||
|
||||
================================================================================
|
||||
RISK ASSESSMENT & MITIGATIONS
|
||||
================================================================================
|
||||
|
||||
RISK PROBABILITY SEVERITY MITIGATION
|
||||
──────────────────────────────────────────────────────────────────
|
||||
HEDD API Access Delayed MEDIUM MEDIUM Scraper fallback
|
||||
GMC Blocks Scraping LOW LOW Request official API
|
||||
Market Adoption Slow MEDIUM HIGH Focus healthcare 1st
|
||||
Regulatory Gatekeeping MEDIUM MEDIUM Partner with vendors early
|
||||
Competitor Response MEDIUM MEDIUM First-mover advantage
|
||||
|
||||
CONTINGENCY PLANS:
|
||||
- If HEDD API denied: Use web portal integration (3-day fallback)
|
||||
- If GMC API denied: Deploy scraper (5-7 days dev)
|
||||
- If market adoption slow: Pivot to healthcare vertical (faster wins)
|
||||
- If regulatory delays: Partner with established vendors (vendor risk)
|
||||
|
||||
================================================================================
|
||||
30-DAY ACTION PLAN
|
||||
================================================================================
|
||||
|
||||
WEEK 1 - SETUP & INITIATION
|
||||
□ Email HEDD (partnerships@hedd.ac.uk) requesting API access
|
||||
□ Email GMC (digital@gmc-uk.org) requesting verification API
|
||||
□ Email NMC requesting verification capabilities
|
||||
□ Allocate 2 engineers to Phase 1 development
|
||||
□ Identify 3-5 recruitment agency beta partners
|
||||
□ Set up development environment
|
||||
|
||||
WEEK 2-3 - DEVELOPMENT BEGINS
|
||||
□ Receive HEDD credentials (or begin scraper development)
|
||||
□ Start HEDD integration development
|
||||
□ Begin Companies House enhancement code
|
||||
□ Begin healthcare register scraper development
|
||||
□ Begin enhanced timeline analysis implementation
|
||||
□ Set up CI/CD pipeline for testing
|
||||
|
||||
WEEK 4 - FEATURE INTEGRATION
|
||||
□ Complete HEDD client and verification service
|
||||
□ Complete healthcare register scrapers
|
||||
□ Complete timeline analysis enhancement
|
||||
□ Complete director verification service
|
||||
□ Deploy to test environment
|
||||
|
||||
WEEK 5 - BETA TESTING
|
||||
□ Deploy beta environment
|
||||
□ Onboard beta partner agencies (3-5 companies)
|
||||
□ Conduct user testing
|
||||
□ Collect feedback on UX and feature value
|
||||
□ Document edge cases and issues
|
||||
|
||||
WEEK 6-7 - REFINEMENT
|
||||
□ Iterate based on beta feedback
|
||||
□ Fix bugs and refine accuracy
|
||||
□ Finalize UI/UX for public version
|
||||
□ Prepare marketing materials
|
||||
□ Brief sales team on features
|
||||
|
||||
WEEK 8 - PUBLIC LAUNCH
|
||||
□ Final QA sign-off
|
||||
□ Deploy to production
|
||||
□ Public launch announcement
|
||||
□ Press/analyst outreach
|
||||
□ Sales outreach to prospects
|
||||
□ Monitor for production issues
|
||||
|
||||
================================================================================
|
||||
SUCCESS CRITERIA (POST-PHASE 1)
|
||||
================================================================================
|
||||
|
||||
MUST-HAVES (Gate for Phase 2):
|
||||
✓ HEDD integration live and functional
|
||||
✓ Timeline fraud detection enhanced
|
||||
✓ Companies House director verification working
|
||||
✓ GMC/NMC healthcare checks live
|
||||
✓ 500+ public signups within first month
|
||||
✓ 10%+ weekly active check rate
|
||||
✓ <5% API error rate
|
||||
✓ Zero critical production incidents
|
||||
|
||||
NICE-TO-HAVES (Excellence targets):
|
||||
✓ 85%+ user satisfaction score (NPS >40)
|
||||
✓ Media/analyst coverage
|
||||
✓ 5+ paying customers (£2-5K MRR)
|
||||
✓ Documented case studies
|
||||
✓ 10+ recruitment agencies in beta
|
||||
|
||||
RED FLAGS (Reassess strategy if occurring):
|
||||
✗ <100 signups after public launch
|
||||
✗ HEDD access denied AND scraper fails
|
||||
✗ <3 beta partners willing to participate
|
||||
✗ >10% API error rate or frequent outages
|
||||
✗ <20% weekly active rate
|
||||
✗ Market research shows insufficient demand
|
||||
|
||||
================================================================================
|
||||
NEXT STEPS FOR STAKEHOLDERS
|
||||
================================================================================
|
||||
|
||||
FOR EXECUTIVES/INVESTORS:
|
||||
1. Review EXECUTIVE_SUMMARY.md (5 minutes)
|
||||
2. Review QUICK_REFERENCE.md for metrics (3 minutes)
|
||||
3. Review Financial Projections in UK_MARKET_STRATEGY.md (5 minutes)
|
||||
4. DECISION: Approve Phase 1 go-ahead? (Yes/No)
|
||||
5. If YES: Approve budget (£40-50K) and resource allocation
|
||||
|
||||
FOR PRODUCT MANAGERS:
|
||||
1. Read entire UK_FEATURE_PRIORITIZATION.md
|
||||
2. Read GTM section of UK_MARKET_STRATEGY.md
|
||||
3. Read API_RESOURCES_AND_CONTACTS.md for API status
|
||||
4. Begin API access coordination (email HEDD, GMC, NMC)
|
||||
5. Start recruiting beta partners this week
|
||||
|
||||
FOR ENGINEERING LEADS:
|
||||
1. Read PHASE1_TECHNICAL_IMPLEMENTATION.md (full - 40+ minutes)
|
||||
2. Read API_RESOURCES_AND_CONTACTS.md (full - 20+ minutes)
|
||||
3. Study the 4 code examples for each feature
|
||||
4. Set up development environment
|
||||
5. Plan sprint structure for 8-week delivery
|
||||
6. Identify any blockers or concerns
|
||||
7. Brief team on Phase 1 scope and timeline
|
||||
|
||||
FOR SALES/MARKETING:
|
||||
1. Read UK_MARKET_STRATEGY.md (full)
|
||||
2. Review Customer Personas section
|
||||
3. Review GTM Channels section
|
||||
4. Begin designing marketing materials
|
||||
5. Create sales talking points
|
||||
6. Prepare for "degree verification" messaging
|
||||
|
||||
================================================================================
|
||||
DOCUMENT LOCATIONS
|
||||
================================================================================
|
||||
|
||||
All files have been created in: /mnt/d/Git/TrueCV/
|
||||
|
||||
FILE STRUCTURE:
|
||||
/mnt/d/Git/TrueCV/QUICK_REFERENCE.md (Start here)
|
||||
/mnt/d/Git/TrueCV/EXECUTIVE_SUMMARY.md (Execs/investors)
|
||||
/mnt/d/Git/TrueCV/UK_FEATURE_PRIORITIZATION.md (Product)
|
||||
/mnt/d/Git/TrueCV/PHASE1_TECHNICAL_IMPLEMENTATION.md (Engineering)
|
||||
/mnt/d/Git/TrueCV/UK_MARKET_STRATEGY.md (Strategy/Sales/Marketing)
|
||||
/mnt/d/Git/TrueCV/API_RESOURCES_AND_CONTACTS.md (Implementation)
|
||||
/mnt/d/Git/TrueCV/README_UK_STRATEGY.md (Navigation)
|
||||
/mnt/d/Git/TrueCV/INDEX.md (Reference index)
|
||||
|
||||
FILES READY FOR USE IMMEDIATELY.
|
||||
|
||||
================================================================================
|
||||
FINAL RECOMMENDATIONS
|
||||
================================================================================
|
||||
|
||||
STRATEGIC DECISION:
|
||||
✓ STRONGLY RECOMMEND proceeding with Phase 1 immediately
|
||||
|
||||
RATIONALE:
|
||||
✓ Market gap is real and valuable (£3.3M addressable)
|
||||
✓ Competitive advantage is sustainable (6-12 month window)
|
||||
✓ Financial model is attractive (break-even in 6-7 months)
|
||||
✓ Technical feasibility is high (APIs accessible, proven patterns)
|
||||
✓ Team requirements are reasonable (2 engineers for 8 weeks)
|
||||
✓ Risk mitigation strategies are solid (fallbacks in place)
|
||||
|
||||
CRITICAL SUCCESS FACTORS:
|
||||
1. Secure HEDD API access (or verify scraper approach works)
|
||||
2. Recruit 3-5 beta partners committed to testing
|
||||
3. Maintain 8-week timeline (no scope creep)
|
||||
4. Achieve 500+ signups in first month (viral/organic growth)
|
||||
5. Monitor unit economics carefully (adjust pricing if needed)
|
||||
|
||||
NEXT DECISION POINT:
|
||||
Post-Phase 1 (Week 8): Should we proceed to Phase 2 (Q2) and Phase 3 (Q3)?
|
||||
- SUCCESS: Yes, proceed with professional bodies expansion
|
||||
- MODERATE: Yes, but revisit roadmap based on market feedback
|
||||
- WEAK: Pause, reassess market viability
|
||||
|
||||
================================================================================
|
||||
CONTACT INFORMATION
|
||||
================================================================================
|
||||
|
||||
STRATEGIC QUESTIONS:
|
||||
→ Product Leadership: [Assign contact]
|
||||
|
||||
MARKET ANALYSIS QUESTIONS:
|
||||
→ Marketing/Sales: [Assign contact]
|
||||
|
||||
TECHNICAL FEASIBILITY QUESTIONS:
|
||||
→ Engineering Lead: [Assign contact]
|
||||
|
||||
FINANCIAL MODEL QUESTIONS:
|
||||
→ Finance/CFO: [Assign contact]
|
||||
|
||||
================================================================================
|
||||
DOCUMENT VERSION & CONTROL
|
||||
================================================================================
|
||||
|
||||
VERSION: 1.0
|
||||
CREATED: January 20, 2026
|
||||
LAST UPDATED: January 20, 2026
|
||||
STATUS: READY FOR EXECUTION
|
||||
|
||||
DISTRIBUTION: Internal Only
|
||||
RECIPIENTS: Product, Engineering, Leadership
|
||||
|
||||
PLANNED UPDATES:
|
||||
- April 1, 2026: Post-Phase 1 launch retrospective
|
||||
- July 1, 2026: Post-Phase 2 launch update
|
||||
- October 1, 2026: Post-Phase 3 launch update
|
||||
- January 1, 2027: Year 1 retrospective + Year 2 planning
|
||||
|
||||
================================================================================
|
||||
CLOSING NOTES
|
||||
================================================================================
|
||||
|
||||
This strategy document represents a comprehensive analysis of TrueCV's
|
||||
opportunity in the UK CV verification market. It provides:
|
||||
|
||||
✓ Clear market opportunity quantification (£3.3M addressable)
|
||||
✓ Competitive advantage analysis (exclusive features)
|
||||
✓ Detailed technical implementation plans (production-ready code)
|
||||
✓ Go-to-market strategy (4 sales channels)
|
||||
✓ Financial projections (Year 1 break-even)
|
||||
✓ Risk mitigation (contingency plans)
|
||||
✓ 30-day action plan (immediate next steps)
|
||||
|
||||
The strategy is:
|
||||
✓ Data-driven (based on market research and API analysis)
|
||||
✓ Actionable (contains concrete implementation details)
|
||||
✓ Realistic (includes risk assessment and fallbacks)
|
||||
✓ Executable (fits within 8-week Phase 1 timeline)
|
||||
|
||||
Next step: **Leadership decision on Phase 1 go-ahead**
|
||||
|
||||
If approved, Phase 1 can launch by Week 2 of this plan.
|
||||
|
||||
================================================================================
|
||||
END OF DELIVERY SUMMARY
|
||||
================================================================================
|
||||
|
||||
Questions? Contact [Product Leadership]
|
||||
More details? See INDEX.md for document navigation
|
||||
Ready to execute? See 30-DAY ACTION PLAN above
|
||||
|
||||
14
Dockerfile
14
Dockerfile
@@ -3,11 +3,11 @@ FROM mcr.microsoft.com/dotnet/sdk:8.0 AS build
|
||||
WORKDIR /src
|
||||
|
||||
# Copy solution and project files first for better layer caching
|
||||
COPY TrueCV.sln ./
|
||||
COPY src/TrueCV.Domain/TrueCV.Domain.csproj src/TrueCV.Domain/
|
||||
COPY src/TrueCV.Application/TrueCV.Application.csproj src/TrueCV.Application/
|
||||
COPY src/TrueCV.Infrastructure/TrueCV.Infrastructure.csproj src/TrueCV.Infrastructure/
|
||||
COPY src/TrueCV.Web/TrueCV.Web.csproj src/TrueCV.Web/
|
||||
COPY RealCV.sln ./
|
||||
COPY src/RealCV.Domain/RealCV.Domain.csproj src/RealCV.Domain/
|
||||
COPY src/RealCV.Application/RealCV.Application.csproj src/RealCV.Application/
|
||||
COPY src/RealCV.Infrastructure/RealCV.Infrastructure.csproj src/RealCV.Infrastructure/
|
||||
COPY src/RealCV.Web/RealCV.Web.csproj src/RealCV.Web/
|
||||
|
||||
# Restore dependencies
|
||||
RUN dotnet restore
|
||||
@@ -16,7 +16,7 @@ RUN dotnet restore
|
||||
COPY src/ src/
|
||||
|
||||
# Build and publish
|
||||
WORKDIR /src/src/TrueCV.Web
|
||||
WORKDIR /src/src/RealCV.Web
|
||||
RUN dotnet publish -c Release -o /app/publish --no-restore
|
||||
|
||||
# Runtime stage
|
||||
@@ -51,4 +51,4 @@ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
|
||||
CMD curl -f http://localhost:8080/health || exit 1
|
||||
|
||||
# Start the app
|
||||
ENTRYPOINT ["dotnet", "TrueCV.Web.dll"]
|
||||
ENTRYPOINT ["dotnet", "RealCV.Web.dll"]
|
||||
|
||||
@@ -7,11 +7,11 @@ RUN dotnet tool install --global dotnet-ef
|
||||
ENV PATH="$PATH:/root/.dotnet/tools"
|
||||
|
||||
# Copy solution and project files
|
||||
COPY TrueCV.sln ./
|
||||
COPY src/TrueCV.Domain/TrueCV.Domain.csproj src/TrueCV.Domain/
|
||||
COPY src/TrueCV.Application/TrueCV.Application.csproj src/TrueCV.Application/
|
||||
COPY src/TrueCV.Infrastructure/TrueCV.Infrastructure.csproj src/TrueCV.Infrastructure/
|
||||
COPY src/TrueCV.Web/TrueCV.Web.csproj src/TrueCV.Web/
|
||||
COPY RealCV.sln ./
|
||||
COPY src/RealCV.Domain/RealCV.Domain.csproj src/RealCV.Domain/
|
||||
COPY src/RealCV.Application/RealCV.Application.csproj src/RealCV.Application/
|
||||
COPY src/RealCV.Infrastructure/RealCV.Infrastructure.csproj src/RealCV.Infrastructure/
|
||||
COPY src/RealCV.Web/RealCV.Web.csproj src/RealCV.Web/
|
||||
|
||||
# Restore dependencies
|
||||
RUN dotnet restore
|
||||
@@ -20,7 +20,7 @@ RUN dotnet restore
|
||||
COPY src/ src/
|
||||
|
||||
# Build the project
|
||||
RUN dotnet build src/TrueCV.Web/TrueCV.Web.csproj -c Release
|
||||
RUN dotnet build src/RealCV.Web/RealCV.Web.csproj -c Release
|
||||
|
||||
# Run migrations on startup
|
||||
ENTRYPOINT ["dotnet", "ef", "database", "update", "--project", "src/TrueCV.Infrastructure", "--startup-project", "src/TrueCV.Web", "--no-build", "-c", "Release"]
|
||||
ENTRYPOINT ["dotnet", "ef", "database", "update", "--project", "src/RealCV.Infrastructure", "--startup-project", "src/RealCV.Web", "--no-build", "-c", "Release"]
|
||||
|
||||
@@ -1,236 +0,0 @@
|
||||
# TrueCV UK Market Opportunity - Executive Summary
|
||||
|
||||
**Prepared for:** Product Leadership
|
||||
**Date:** January 2026
|
||||
**Priority:** High - Q1 2026 Implementation Opportunity
|
||||
|
||||
---
|
||||
|
||||
## The Opportunity
|
||||
|
||||
**UK CV fraud costs employers £4.2B annually. Current verification takes 5-10 days. TrueCV can do it in seconds.**
|
||||
|
||||
### Market Problem
|
||||
- **1 in 5 UK candidates** falsify university degrees
|
||||
- **24% of screened CVs** fail verification checks
|
||||
- **40% of CV lies** involve qualification exaggeration
|
||||
- **Regulatory risk:** Companies face legal liability in healthcare, finance, legal sectors
|
||||
- **AI-accelerated fraud:** Deepfakes and synthetic identities emerging in 2026
|
||||
|
||||
### Current Solution Gaps
|
||||
- Education verification requires contacting universities individually (10+ days per candidate)
|
||||
- Professional registration checks vary by profession with no central API
|
||||
- No integrated view connecting employment, education, and professional credentials
|
||||
- Manual processes don't scale for high-volume recruiters
|
||||
|
||||
---
|
||||
|
||||
## TrueCV's Solution
|
||||
|
||||
**Integrated CV verification platform leveraging UK-specific data sources:**
|
||||
|
||||
| Feature | Coverage | Implementation | Impact |
|
||||
|---|---|---|---|
|
||||
| **HEDD Degree Verification** | UK degrees (140+ universities) | Q1 2026 (2-3 weeks) | Detects 90%+ of fake degrees |
|
||||
| **Healthcare Registers** (GMC/NMC) | Doctors, nurses, midwives | Q1 2026 (1 week) | Dominates healthcare niche |
|
||||
| **Timeline Fraud Detection** | Employment/education overlaps, gaps | Q1 2026 (1 week) | Catches 80%+ of timeline lies |
|
||||
| **Company Director Verification** | Self-employment claims (Companies House) | Q1 2026 (1-2 weeks) | Validates 15-20% of CVs |
|
||||
| **Professional Bodies** | ICAEW, SRA, IET, RIBA | Q2 2026 (4 weeks) | Expands to regulated professions |
|
||||
| **DBS Integration** | Criminal record checks | Q3 2026 (8 weeks) | Compliance + revenue stream |
|
||||
|
||||
---
|
||||
|
||||
## Competitive Advantage
|
||||
|
||||
**TrueCV is the ONLY CV verification tool that:**
|
||||
|
||||
1. ✅ Integrates with HEDD (no competitors do)
|
||||
2. ✅ Targets healthcare recruiting niche (GMC/NMC)
|
||||
3. ✅ Verifies director claims (Companies House cross-check)
|
||||
4. ✅ Detects timeline fraud across education-employment boundary
|
||||
5. ✅ UK-first approach vs. global platforms
|
||||
|
||||
**Nearest competitors** (Workable, Deel, Checkr) focus on broad background screening, not CV verification.
|
||||
|
||||
---
|
||||
|
||||
## Market Size
|
||||
|
||||
### Addressable Market
|
||||
- **18,300 potential customers** (recruitment agencies + corporate HR)
|
||||
- **£2.8B UK pre-employment screening market**
|
||||
- **~£3.3M serviceable opportunity** for TrueCV platform
|
||||
|
||||
### Year 1 Revenue Target
|
||||
- **50-75 paying customers** at £49-199/month
|
||||
- **£30-240K revenue** Year 1 (conservative-growth scenario)
|
||||
- **£113K-226K annualized** by end of Year 1
|
||||
|
||||
### Unit Economics
|
||||
- **CAC:** £150-300 (organic/partner-led)
|
||||
- **ARPU:** £60-120/month
|
||||
- **Payback:** 2-4 months
|
||||
- **Gross margin:** 75-80%
|
||||
|
||||
---
|
||||
|
||||
## Implementation Plan
|
||||
|
||||
### Phase 1: MVP (Q1 2026 - 8 weeks)
|
||||
**Deliverables:**
|
||||
1. HEDD degree verification (real-time + manual review tracking)
|
||||
2. GMC/NMC healthcare register checks
|
||||
3. Enhanced timeline analysis (education/employment sequencing)
|
||||
4. Companies House director verification
|
||||
5. Public beta launch
|
||||
|
||||
**Team:** 2 backend engineers, 1 QA, 1 PM, 1 marketing
|
||||
|
||||
**Go-to-Market:** Beta with 3-5 recruitment agencies → Public launch → Press/analyst outreach
|
||||
|
||||
**Success Metrics:**
|
||||
- 500+ signups in first month
|
||||
- 10%+ weekly active check rate
|
||||
- 85%+ feature satisfaction
|
||||
|
||||
### Phase 2: Professional Bodies (Q2 2026)
|
||||
- ICAEW, SRA, IET, RIBA integration
|
||||
- Vertical market expansion
|
||||
- +40% user growth
|
||||
- +3x engagement
|
||||
|
||||
### Phase 3: Compliance & Regulatory (Q3 2026)
|
||||
- HMRC payroll verification
|
||||
- DBS check integration (partnership)
|
||||
- Enterprise/platform tier
|
||||
- Recurring revenue + commission model
|
||||
|
||||
---
|
||||
|
||||
## Key Dependencies & Risks
|
||||
|
||||
### Critical Path Items
|
||||
1. ✅ **HEDD API Access** - Start conversations NOW
|
||||
- Fallback: Web portal integration (still works)
|
||||
- Timeline: Approval expected 2-4 weeks
|
||||
|
||||
2. ✅ **GMC/NMC Verification** - Request official API access
|
||||
- Fallback: Web scraper (more fragile)
|
||||
- Timeline: Scraper approach = 1 week dev
|
||||
|
||||
3. ✅ **Companies House API** - Already have access
|
||||
- Enhancement to existing client = 1-2 weeks
|
||||
|
||||
### Risks & Mitigations
|
||||
|
||||
| Risk | Probability | Mitigation |
|
||||
|---|---|---|
|
||||
| HEDD API delayed | Medium | Use web portal integration in parallel |
|
||||
| GMC/NMC scraper blocked | Low | Request official API; provide value-add |
|
||||
| Slow market adoption | Medium | Focus vertically (healthcare first) |
|
||||
| Regulatory gatekeeping | Medium | Partner with established vendors early |
|
||||
|
||||
---
|
||||
|
||||
## Financial Projections
|
||||
|
||||
### Conservative Scenario (50 customers)
|
||||
- **MRR (end-of-year):** £9,455
|
||||
- **Annualized:** £113,460
|
||||
- **Costs:** £220K/year
|
||||
- **Result:** Approaching break-even
|
||||
|
||||
### Growth Scenario (100 customers)
|
||||
- **MRR (end-of-year):** £18,910
|
||||
- **Annualized:** £226,920
|
||||
- **Gross margin:** 75% = £170K+ operational profit
|
||||
- **Result:** Profitable by month 9
|
||||
|
||||
### Break-even Point
|
||||
- **Customers needed:** 24-30 at blended ARPU of £80/month
|
||||
- **Timeline:** Expected by month 6-7
|
||||
|
||||
---
|
||||
|
||||
## Why Now?
|
||||
|
||||
1. **Market Conditions Perfect:**
|
||||
- UK CV fraud peaking (1 in 5 have fake degrees)
|
||||
- AI-generated fraud emerging (accelerating urgency)
|
||||
- HEDD now mature platform (140+ universities on network)
|
||||
- GMC/NMC registers fully digital (scraping viable)
|
||||
|
||||
2. **No Competitive Threat:**
|
||||
- Workable/Deel don't focus on CV verification
|
||||
- Checkr/Verifile are manual processes
|
||||
- No integrated UK player in market
|
||||
|
||||
3. **Technical Feasibility:**
|
||||
- HEDD integration straightforward
|
||||
- Scraper patterns proven (NHS, Companies House)
|
||||
- Timeline analysis already implemented
|
||||
|
||||
---
|
||||
|
||||
## Recommendation
|
||||
|
||||
**PROCEED with Phase 1 implementation immediately.**
|
||||
|
||||
### Next 30 Days
|
||||
- [ ] Secure HEDD API access (contact Prospects/Jisc)
|
||||
- [ ] Recruit 3-5 beta partner agencies
|
||||
- [ ] Begin HEDD client development (Day 1)
|
||||
- [ ] Finalize consent/compliance workflows
|
||||
|
||||
### Success Metrics (Month 1)
|
||||
- HEDD integration code complete
|
||||
- 3+ beta partners onboarded
|
||||
- Timeline analysis enhanced
|
||||
- Public beta announcement scheduled
|
||||
|
||||
### Decision Gate (Month 2)
|
||||
- Evaluate adoption rate (target: 100+ signups)
|
||||
- Assess feature-market fit with agencies
|
||||
- Validate revenue model (pricing feedback)
|
||||
- Proceed to Phase 2 if metrics green
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Detailed Feature Priorities
|
||||
|
||||
### Ranked by: Detection Value × Implementation Feasibility
|
||||
|
||||
**TIER 1 (Start immediately):**
|
||||
1. HEDD Degree Verification - 9.5/10 impact, medium effort
|
||||
2. Timeline Fraud Detection - 7/10 impact, low effort (extending existing)
|
||||
3. GMC/NMC Healthcare Registers - 6.5/10 impact, low effort
|
||||
|
||||
**TIER 2 (Weeks 3-5):**
|
||||
4. Companies House Director Verification - 7.5/10 impact, low effort (extending existing)
|
||||
|
||||
**TIER 3 (Q2):**
|
||||
5. Professional Body Registers - 6.5-7/10 impact, medium effort
|
||||
6. GOV.UK Regulated Professions Enrichment - 5/10 impact, very low effort
|
||||
|
||||
**TIER 4 (Q3+):**
|
||||
7. HMRC Payroll Verification - 9/10 impact, high effort (partnership required)
|
||||
8. DBS Integration - 8.5/10 impact, high effort (partnership + compliance)
|
||||
|
||||
---
|
||||
|
||||
## Questions for Stakeholders
|
||||
|
||||
1. **Timeline:** Can we commit 2 engineers full-time for 8 weeks?
|
||||
2. **HEDD Access:** Will you sponsor approach to HEDD/Prospects for API partnership?
|
||||
3. **Beta Partners:** Do we have recruitment agency relationships for beta testing?
|
||||
4. **Revenue Model:** Approve tiered pricing (Free/Pro/Enterprise)?
|
||||
5. **International:** After UK success, expand to Ireland/Australia?
|
||||
|
||||
---
|
||||
|
||||
## Contact & Next Steps
|
||||
|
||||
**Product Lead:** [Name] - TrueCV Product Strategy
|
||||
**Engineering Lead:** [Name] - Phase 1 Technical Implementation
|
||||
|
||||
**Next Meeting:** [Date] - Review technical implementation plan + finalize go-to-market
|
||||
492
INDEX.md
492
INDEX.md
@@ -1,492 +0,0 @@
|
||||
# TrueCV UK Strategy - Complete Document Index
|
||||
|
||||
**Total Documents:** 6 comprehensive strategy guides
|
||||
**Total Pages:** ~200 pages
|
||||
**Total Read Time:** 2-3 hours (depending on role)
|
||||
|
||||
---
|
||||
|
||||
## Document Inventory
|
||||
|
||||
### 1. **QUICK_REFERENCE.md** ⭐ START HERE (5 minutes)
|
||||
**Size:** 3 pages
|
||||
**Format:** Quick-reference card format
|
||||
**Best For:** All audiences - desk reference
|
||||
**Contains:**
|
||||
- Market opportunity summary
|
||||
- Competitive advantage matrix
|
||||
- Phase 1 timeline (visual)
|
||||
- Feature ranking (visual)
|
||||
- API status table
|
||||
- Pricing strategy
|
||||
- Success metrics
|
||||
- Fraud detection coverage
|
||||
- 30-day action plan
|
||||
- Team requirements
|
||||
- Risk dashboard
|
||||
- Customer personas (condensed)
|
||||
- Sales channels
|
||||
- Decision framework
|
||||
- Key contacts
|
||||
|
||||
**Action Items:** Print this for your desk during planning
|
||||
|
||||
---
|
||||
|
||||
### 2. **EXECUTIVE_SUMMARY.md** (5-10 minutes)
|
||||
**Size:** 5 pages
|
||||
**Format:** Executive brief
|
||||
**Best For:** Executives, Investors, Decision-makers
|
||||
**Contains:**
|
||||
- Market opportunity (£4.2B fraud cost)
|
||||
- Problem statement
|
||||
- Solution overview (4 features)
|
||||
- Competitive advantage
|
||||
- Market size & revenue targets
|
||||
- Implementation plan (3 phases)
|
||||
- Key dependencies & risks
|
||||
- Financial projections (2 scenarios)
|
||||
- Why now analysis
|
||||
- Recommendation
|
||||
- Appendix with detailed feature priorities
|
||||
|
||||
**Next Document:** UK_MARKET_STRATEGY.md (for GTM details)
|
||||
|
||||
---
|
||||
|
||||
### 3. **UK_FEATURE_PRIORITIZATION.md** (20-30 minutes)
|
||||
**Size:** 25-30 pages
|
||||
**Format:** Detailed technical analysis
|
||||
**Best For:** Product Managers, Engineering Leads
|
||||
**Contains:**
|
||||
- Executive summary
|
||||
- Market context (fraud patterns & statistics)
|
||||
- Available UK data sources (detailed analysis of 8 APIs)
|
||||
- Ranked feature prioritization (matrix format)
|
||||
- Recommended implementation roadmap (3 phases)
|
||||
- Feature implementation examples (code pseudocode)
|
||||
- Success metrics per feature
|
||||
- Risk mitigation strategies
|
||||
- UK market positioning statement
|
||||
- API accessibility summary table
|
||||
- Next steps (this week actions)
|
||||
|
||||
**Key Section:** "Ranked Feature Prioritization" (clear action priorities)
|
||||
|
||||
---
|
||||
|
||||
### 4. **PHASE1_TECHNICAL_IMPLEMENTATION.md** (40-50 minutes)
|
||||
**Size:** 50-60 pages
|
||||
**Format:** Complete technical specification + code
|
||||
**Best For:** Backend Engineers, QA Engineers
|
||||
**Contains:**
|
||||
- Phase 1 overview (8-week timeline)
|
||||
- Feature 1: HEDD Integration (complete implementation)
|
||||
- Architecture diagram
|
||||
- 5 complete C# files (interfaces, services, models)
|
||||
- Configuration examples
|
||||
- Database considerations
|
||||
- Feature 2: Enhanced Timeline Analysis (code example)
|
||||
- Feature 3: Companies House Director Verification (code example)
|
||||
- Feature 4: Healthcare Register Scrapers (code example)
|
||||
- Dependency injection setup
|
||||
- Integration examples
|
||||
- Testing strategy
|
||||
- Configuration checklist
|
||||
- Database migration guide
|
||||
- Validation checklist
|
||||
|
||||
**Most Valuable:** "Phase 1a-1g" sections with production-ready code
|
||||
|
||||
---
|
||||
|
||||
### 5. **UK_MARKET_STRATEGY.md** (30-40 minutes)
|
||||
**Size:** 40-50 pages
|
||||
**Format:** Comprehensive market strategy document
|
||||
**Best For:** Product Team, Marketing, Sales, Leadership
|
||||
**Contains:**
|
||||
- Market opportunity analysis
|
||||
- Problem statement (detailed)
|
||||
- Market sizing (TAM/SAM/SOM)
|
||||
- Competitive landscape (8 competitors analyzed)
|
||||
- Product strategy (3 phases)
|
||||
- Go-to-market strategy (4 sales channels)
|
||||
- Pricing strategy (tier analysis + unit economics)
|
||||
- Organizational requirements (team structure over 12 months)
|
||||
- Key risks & mitigations
|
||||
- 12-month success metrics
|
||||
- Financial projections (conservative + growth scenarios)
|
||||
- Next 30 days action plan
|
||||
- Long-term vision (2-3 years, international expansion)
|
||||
- Customer personas (3 detailed profiles)
|
||||
- References & data sources
|
||||
|
||||
**Most Valuable:** Financials section + Customer personas
|
||||
|
||||
---
|
||||
|
||||
### 6. **API_RESOURCES_AND_CONTACTS.md** (20-30 minutes)
|
||||
**Size:** 20-25 pages
|
||||
**Format:** Practical implementation guide
|
||||
**Best For:** Engineering + Product (implementation phase)
|
||||
**Contains:**
|
||||
- Detailed guide for 11 UK APIs
|
||||
- HEDD (degree verification)
|
||||
- GMC Register (doctors)
|
||||
- NMC Register (nurses)
|
||||
- Companies House (already integrated)
|
||||
- GOV.UK Regulated Professions
|
||||
- ICAEW (accountants)
|
||||
- SRA (lawyers)
|
||||
- IET (engineers)
|
||||
- HCPC (health professionals)
|
||||
- DBS Integration (partnership model)
|
||||
- HMRC Payroll (restricted access)
|
||||
- For each API:
|
||||
- Overview & coverage
|
||||
- Access information
|
||||
- Contact details
|
||||
- Integration methods (API vs. scraper)
|
||||
- Required registration info
|
||||
- Timeline for access
|
||||
- Documentation links
|
||||
- Implementation prioritization table
|
||||
- Compliance & data protection checklist
|
||||
- Email template for API requests
|
||||
- Contact information for vendors
|
||||
|
||||
**Most Valuable:** For getting API access - everything you need to know
|
||||
|
||||
---
|
||||
|
||||
### 7. **README_UK_STRATEGY.md** (5-10 minutes)
|
||||
**Size:** 8-10 pages
|
||||
**Format:** Navigation guide + executive summary
|
||||
**Best For:** Navigation & orientation for all audiences
|
||||
**Contains:**
|
||||
- Document hierarchy
|
||||
- Quick navigation by role
|
||||
- Key metrics at a glance
|
||||
- Fraud detection coverage
|
||||
- Competitive moat explanation
|
||||
- Critical path dependencies
|
||||
- Financial summary
|
||||
- Risk mitigation
|
||||
- 30-day plan (high-level)
|
||||
- Recommended reading order
|
||||
- Questions & decision points
|
||||
- Success criteria
|
||||
- File manifest
|
||||
- Version history
|
||||
- Key definitions & terminology
|
||||
- License & confidentiality notice
|
||||
|
||||
**Most Valuable:** Orientation + role-based reading paths
|
||||
|
||||
---
|
||||
|
||||
## Role-Based Reading Guide
|
||||
|
||||
### For Executives (20-30 minutes total)
|
||||
1. **QUICK_REFERENCE.md** (5 min) - Print & keep at desk
|
||||
2. **EXECUTIVE_SUMMARY.md** (10 min) - Full read
|
||||
3. **UK_MARKET_STRATEGY.md** (10 min) - Market Opportunity + Financials sections only
|
||||
4. **Decision:** Approve Phase 1 go-ahead?
|
||||
|
||||
---
|
||||
|
||||
### For Product Managers (90 minutes total)
|
||||
1. **QUICK_REFERENCE.md** (5 min) - Orientation
|
||||
2. **EXECUTIVE_SUMMARY.md** (10 min) - Full read
|
||||
3. **UK_FEATURE_PRIORITIZATION.md** (35 min) - Full read (PRIORITY)
|
||||
4. **UK_MARKET_STRATEGY.md** (25 min) - GTM + Personas sections
|
||||
5. **API_RESOURCES_AND_CONTACTS.md** (10 min) - Skim for reference
|
||||
6. **PHASE1_TECHNICAL_IMPLEMENTATION.md** (5 min) - Skim architecture section only
|
||||
|
||||
---
|
||||
|
||||
### For Backend Engineers (2 hours total)
|
||||
1. **QUICK_REFERENCE.md** (5 min) - Context
|
||||
2. **EXECUTIVE_SUMMARY.md** (5 min) - Skim only
|
||||
3. **PHASE1_TECHNICAL_IMPLEMENTATION.md** (70 min) - FULL READ + study code
|
||||
4. **API_RESOURCES_AND_CONTACTS.md** (30 min) - FULL READ + bookmark sections
|
||||
5. **UK_FEATURE_PRIORITIZATION.md** (10 min) - Implementation examples section
|
||||
|
||||
---
|
||||
|
||||
### For QA Engineers (60 minutes total)
|
||||
1. **QUICK_REFERENCE.md** (5 min)
|
||||
2. **EXECUTIVE_SUMMARY.md** (5 min)
|
||||
3. **UK_FEATURE_PRIORITIZATION.md** (20 min) - Success metrics section
|
||||
4. **PHASE1_TECHNICAL_IMPLEMENTATION.md** (30 min) - Testing & validation sections
|
||||
|
||||
---
|
||||
|
||||
### For Sales/Marketing (60 minutes total)
|
||||
1. **QUICK_REFERENCE.md** (5 min) - Print one
|
||||
2. **EXECUTIVE_SUMMARY.md** (10 min)
|
||||
3. **UK_MARKET_STRATEGY.md** (35 min) - GTM Strategy + Customer Personas + Messaging
|
||||
4. **API_RESOURCES_AND_CONTACTS.md** (5 min) - Quick skim
|
||||
|
||||
---
|
||||
|
||||
### For Investors/Analysts (45 minutes total)
|
||||
1. **EXECUTIVE_SUMMARY.md** (10 min) - FULL READ
|
||||
2. **UK_MARKET_STRATEGY.md** (30 min) - Market Opportunity, Financials, Competitive Landscape sections
|
||||
3. **QUICK_REFERENCE.md** (5 min) - Risk dashboard
|
||||
|
||||
---
|
||||
|
||||
## Document Cross-References
|
||||
|
||||
### How to Find Information About...
|
||||
|
||||
**HEDD Integration:**
|
||||
- Quick overview → QUICK_REFERENCE.md (API Integration Status table)
|
||||
- Strategic importance → EXECUTIVE_SUMMARY.md
|
||||
- Detailed analysis → UK_FEATURE_PRIORITIZATION.md (Section: "HEDD Degree Verification")
|
||||
- Technical implementation → PHASE1_TECHNICAL_IMPLEMENTATION.md (Feature 1)
|
||||
- How to get access → API_RESOURCES_AND_CONTACTS.md (Section 1)
|
||||
|
||||
**Market Opportunity:**
|
||||
- Quick version → QUICK_REFERENCE.md (Market Opportunity section)
|
||||
- Detailed analysis → EXECUTIVE_SUMMARY.md (full document)
|
||||
- Go-to-market strategy → UK_MARKET_STRATEGY.md (full document)
|
||||
- Customer research → UK_MARKET_STRATEGY.md (Customer Personas)
|
||||
|
||||
**Financial Projections:**
|
||||
- Quick reference → QUICK_REFERENCE.md (Financial Projections)
|
||||
- Conservative scenario → EXECUTIVE_SUMMARY.md
|
||||
- Detailed projections → UK_MARKET_STRATEGY.md (Financial Projections section)
|
||||
- Unit economics → UK_MARKET_STRATEGY.md (Pricing Strategy)
|
||||
|
||||
**Timeline & Roadmap:**
|
||||
- Quick version → QUICK_REFERENCE.md (Phase 1 Roadmap)
|
||||
- Executive summary → EXECUTIVE_SUMMARY.md (Implementation Plan)
|
||||
- Detailed roadmap → UK_MARKET_STRATEGY.md (3-phase strategy)
|
||||
- Technical details → PHASE1_TECHNICAL_IMPLEMENTATION.md (entire document)
|
||||
|
||||
**Competitive Advantage:**
|
||||
- Matrix format → QUICK_REFERENCE.md (Competitive Advantage)
|
||||
- Analysis → EXECUTIVE_SUMMARY.md (Why Now section)
|
||||
- Detailed analysis → UK_MARKET_STRATEGY.md (Competitive Landscape)
|
||||
- Moat explanation → README_UK_STRATEGY.md
|
||||
|
||||
**Team & Resources:**
|
||||
- Summary → QUICK_REFERENCE.md (Team Requirements)
|
||||
- Detailed breakdown → UK_MARKET_STRATEGY.md (Organizational Requirements)
|
||||
- Implementation details → PHASE1_TECHNICAL_IMPLEMENTATION.md
|
||||
|
||||
**Risk Management:**
|
||||
- Dashboard → QUICK_REFERENCE.md (Risk Dashboard)
|
||||
- Brief mitigation → EXECUTIVE_SUMMARY.md (Key Dependencies & Risks)
|
||||
- Detailed analysis → UK_FEATURE_PRIORITIZATION.md (Risk Mitigation)
|
||||
- Market risks → UK_MARKET_STRATEGY.md (Key Risks & Mitigations)
|
||||
|
||||
**API Information:**
|
||||
- Status table → QUICK_REFERENCE.md (API Integration Status)
|
||||
- Accessibility overview → UK_FEATURE_PRIORITIZATION.md (API Accessibility Summary)
|
||||
- Detailed guides → API_RESOURCES_AND_CONTACTS.md (entire document)
|
||||
|
||||
---
|
||||
|
||||
## Information Density by Document
|
||||
|
||||
| Document | Pages | Detail Level | Best For |
|
||||
|---|---|---|---|
|
||||
| QUICK_REFERENCE.md | 3-4 | High density, visual | Desk reference |
|
||||
| EXECUTIVE_SUMMARY.md | 5-6 | Medium density | Decision-making |
|
||||
| UK_FEATURE_PRIORITIZATION.md | 25-30 | High detail | Strategy execution |
|
||||
| PHASE1_TECHNICAL_IMPLEMENTATION.md | 50-60 | Very high detail | Engineering |
|
||||
| UK_MARKET_STRATEGY.md | 40-50 | High detail | Business strategy |
|
||||
| API_RESOURCES_AND_CONTACTS.md | 20-25 | Reference style | API integration |
|
||||
| README_UK_STRATEGY.md | 8-10 | Navigation focus | Finding information |
|
||||
|
||||
---
|
||||
|
||||
## Key Metrics Summary
|
||||
|
||||
All documents contain these key metrics:
|
||||
|
||||
**Market Opportunity:**
|
||||
- TAM: £3.3M (UK recruitment market)
|
||||
- Year 1 Revenue Target: £30-240K
|
||||
- Break-even: 24-30 customers
|
||||
- Profitability: Month 6-7
|
||||
|
||||
**Fraud Coverage:**
|
||||
- HEDD: 90%+ detection of fake degrees
|
||||
- Timeline: 80%+ detection of employment date fraud
|
||||
- Healthcare: 95%+ detection of GMC/NMC fraud
|
||||
|
||||
**Success Metrics:**
|
||||
- Signups: 500+ in first month
|
||||
- Active rate: 10%+ weekly
|
||||
- Feature satisfaction: 85%+
|
||||
- Uptime: 99.9%
|
||||
|
||||
**Financial Unit Economics:**
|
||||
- CAC: £150-300
|
||||
- ARPU: £60-120/month
|
||||
- Payback: 2-4 months
|
||||
- Gross margin: 75-80%
|
||||
|
||||
---
|
||||
|
||||
## Action Items Checklist
|
||||
|
||||
After reading these documents, complete:
|
||||
|
||||
### Week 1 (Critical Path)
|
||||
- [ ] Read EXECUTIVE_SUMMARY.md + QUICK_REFERENCE.md
|
||||
- [ ] Decide: Proceed with Phase 1?
|
||||
- [ ] If YES: Send API access requests (HEDD, GMC, NMC)
|
||||
- [ ] If YES: Allocate 2 engineers to Phase 1
|
||||
- [ ] If YES: Identify 3-5 beta partner recruitment agencies
|
||||
|
||||
### Week 2-3
|
||||
- [ ] Read PHASE1_TECHNICAL_IMPLEMENTATION.md (if engineering)
|
||||
- [ ] Read UK_FEATURE_PRIORITIZATION.md (if product)
|
||||
- [ ] Receive API access credentials or begin scraper development
|
||||
- [ ] Set up development environment
|
||||
- [ ] Begin Phase 1 coding
|
||||
|
||||
### Week 4-8
|
||||
- [ ] Execute Phase 1 development plan
|
||||
- [ ] Deploy beta environment
|
||||
- [ ] Conduct user testing with beta partners
|
||||
- [ ] Iterate based on feedback
|
||||
- [ ] Plan public launch
|
||||
|
||||
---
|
||||
|
||||
## Version Control & Updates
|
||||
|
||||
**Current Version:** 1.0 - January 20, 2026
|
||||
|
||||
**Planned Updates:**
|
||||
- **April 2026:** Post-Phase 1 launch review
|
||||
- **July 2026:** Post-Phase 2 launch update (Professional Bodies)
|
||||
- **October 2026:** Post-Phase 3 launch update (Compliance/Regulatory)
|
||||
- **January 2027:** Year 1 retrospective + Year 2 planning
|
||||
|
||||
**Update Process:**
|
||||
1. Incorporate market feedback from beta/paying customers
|
||||
2. Update financial projections with actual metrics
|
||||
3. Revise timeline based on actual delivery vs. plan
|
||||
4. Add new competitive intelligence
|
||||
5. Update feature roadmap based on customer demand
|
||||
|
||||
---
|
||||
|
||||
## Document Quality Standards
|
||||
|
||||
Each document has been:
|
||||
- ✅ Reviewed for accuracy (market data, API info current as of Jan 2026)
|
||||
- ✅ Checked for internal consistency (numbers align across docs)
|
||||
- ✅ Tested for completeness (all major topics covered)
|
||||
- ✅ Formatted for easy navigation (clear sections, tables, links)
|
||||
- ✅ Validated for actionability (contains concrete next steps)
|
||||
|
||||
---
|
||||
|
||||
## Copyright & Distribution
|
||||
|
||||
**Ownership:** TrueCV Product Team
|
||||
**Classification:** Internal Only
|
||||
**Distribution:** Leadership, Product, Engineering only
|
||||
|
||||
**Permitted Use:**
|
||||
- Internal strategic planning ✅
|
||||
- Cross-functional alignment meetings ✅
|
||||
- Board presentations ✅
|
||||
- Investor pitches ✅
|
||||
|
||||
**Prohibited Use:**
|
||||
- Public distribution ❌
|
||||
- Sharing with competitors ❌
|
||||
- Press releases without approval ❌
|
||||
- Posting on public repositories ❌
|
||||
|
||||
---
|
||||
|
||||
## Support & Questions
|
||||
|
||||
**For questions about this strategy:**
|
||||
|
||||
- **Strategy Overview:** Product Manager
|
||||
- **Market Analysis:** Marketing Lead
|
||||
- **Technical Implementation:** Engineering Lead
|
||||
- **Financial Model:** CFO/Finance
|
||||
- **Customer Personas:** Sales Lead
|
||||
- **API Integration:** Technical Lead
|
||||
|
||||
**Document Feedback:**
|
||||
- Report errors: [Internal feedback channel]
|
||||
- Suggest additions: [Internal feedback channel]
|
||||
- Request updates: [Internal feedback channel]
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Quick Stats
|
||||
|
||||
```
|
||||
MARKET OPPORTUNITY
|
||||
UK CV fraud cost: £4.2B annually
|
||||
Addressable market: £3.3M (TrueCV's portion)
|
||||
Candidates lying: 1 in 5 (20%)
|
||||
Failed verifications: 24% of CVs
|
||||
Current verification time: 5-10 DAYS
|
||||
|
||||
COMPETITIVE ADVANTAGE
|
||||
Features only TrueCV offers: 4 major features
|
||||
Market gap size: Unexploited (£3.3M)
|
||||
Time to market advantage: 6-12 months
|
||||
|
||||
PHASE 1 DELIVERY
|
||||
Timeline: 8 weeks (Q1 2026)
|
||||
Features: 4 major features
|
||||
Team: 2 engineers + 1 QA
|
||||
APIs integrated: 5 new integrations
|
||||
Testing: >90% code coverage
|
||||
|
||||
YEAR 1 TARGETS
|
||||
Revenue (conservative): £113K (50 customers)
|
||||
Revenue (growth): £227K (100 customers)
|
||||
Break-even: 24-30 customers (Month 6-7)
|
||||
Profitability: Month 8-9 (if growth scenario)
|
||||
|
||||
SUCCESS METRICS
|
||||
Signups month 1: 500+
|
||||
Weekly active rate: 10%+
|
||||
Feature satisfaction: 85%+
|
||||
Uptime: 99.9%
|
||||
|
||||
TEAM REQUIREMENTS
|
||||
Phase 1 (Q1): 3 engineers + 1 PM + 1 marketing
|
||||
Phase 2 (Q2): +1 engineer + 1 sales/BD
|
||||
Phase 3 (Q3): +1 customer success + 1 analyst
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Next Review:** April 1, 2026
|
||||
**Status:** READY FOR EXECUTION
|
||||
|
||||
---
|
||||
|
||||
## Quick Links
|
||||
|
||||
**Files Created:**
|
||||
- `/mnt/d/Git/TrueCV/QUICK_REFERENCE.md`
|
||||
- `/mnt/d/Git/TrueCV/EXECUTIVE_SUMMARY.md`
|
||||
- `/mnt/d/Git/TrueCV/UK_FEATURE_PRIORITIZATION.md`
|
||||
- `/mnt/d/Git/TrueCV/PHASE1_TECHNICAL_IMPLEMENTATION.md`
|
||||
- `/mnt/d/Git/TrueCV/UK_MARKET_STRATEGY.md`
|
||||
- `/mnt/d/Git/TrueCV/API_RESOURCES_AND_CONTACTS.md`
|
||||
- `/mnt/d/Git/TrueCV/README_UK_STRATEGY.md`
|
||||
- `/mnt/d/Git/TrueCV/INDEX.md` (this file)
|
||||
|
||||
**Total:** 8 comprehensive strategy documents (~200 pages)
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,380 +0,0 @@
|
||||
# TrueCV UK Strategy - Quick Reference Card
|
||||
|
||||
**Print this page for desk reference during planning & execution**
|
||||
|
||||
---
|
||||
|
||||
## Market Opportunity (The Why)
|
||||
|
||||
```
|
||||
UK CV Fraud Cost: £4.2B annually
|
||||
Candidates Lying: 1 in 5 (20%)
|
||||
Failed Verifications: 24% of CVs
|
||||
Current Verification Time: 5-10 DAYS
|
||||
TrueCV Solution Time: 5 SECONDS ⚡
|
||||
|
||||
Market Addressable: £3.3M (UK)
|
||||
Year 1 Target Revenue: £30-240K
|
||||
Break-even Customers: 24-30
|
||||
Expected Profitability: Month 6-7
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Competitive Advantage (Why Now)
|
||||
|
||||
```
|
||||
FEATURE TrueCV Workable Deel Checkr
|
||||
─────────────────────────────────────────────────────────
|
||||
HEDD Degree Verification ✅ ❌ ❌ ❌
|
||||
GMC/NMC Healthcare ✅ ❌ ❌ ❌
|
||||
Timeline Fraud Detection ✅ ❌ ❌ ❌
|
||||
Director Verification ✅ ❌ ❌ ❌
|
||||
Companies House API ✅ ❌ ❌ ❌
|
||||
|
||||
CONCLUSION: Only player with integrated UK CV verification
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Phase 1 Roadmap (Q1 2026 - 8 Weeks)
|
||||
|
||||
```
|
||||
Week 1-2: SETUP
|
||||
├─ Secure API access (HEDD, GMC, NMC)
|
||||
├─ Allocate 2 engineers
|
||||
└─ Recruit 3-5 beta partners
|
||||
|
||||
Week 2-4: DEVELOPMENT (Parallel Tracks)
|
||||
├─ HEDD integration (Lead eng: 2-3 weeks)
|
||||
├─ Healthcare registers (Sec eng: 1 week)
|
||||
├─ Companies House enhancement (Sec eng: 1-2 weeks)
|
||||
└─ Timeline analysis (Tertiary: 1 week)
|
||||
|
||||
Week 5-7: BETA TESTING
|
||||
├─ Deploy to test environment
|
||||
├─ Onboard beta agencies
|
||||
├─ Collect feedback
|
||||
└─ Iterate on UX/flags
|
||||
|
||||
Week 8: PUBLIC LAUNCH
|
||||
├─ GA release
|
||||
├─ Marketing campaign
|
||||
├─ Press/analyst outreach
|
||||
└─ Sales outreach begins
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Features Ranked by Impact × Feasibility
|
||||
|
||||
```
|
||||
HIGH IMPACT + EASY ─────────────┬─ HIGH IMPACT + HARD
|
||||
│
|
||||
1. HEDD Verification (9.5/10) │ 8. DBS Integration (Q3)
|
||||
2. Timeline Analysis (7/10) │ 5. HMRC Payroll (Q3)
|
||||
3. GMC/NMC Checks (6.5/10) │ 6. Professional Bodies
|
||||
4. Director Verify (7.5/10) │
|
||||
│
|
||||
MEDIUM IMPACT + EASY ───────────┼─ MEDIUM IMPACT + HARD
|
||||
│
|
||||
7. GOV.UK Registry (5/10) │
|
||||
│
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Integration Status
|
||||
|
||||
| Service | Type | Access | Effort | Timeline |
|
||||
|---|---|---|---|---|
|
||||
| **HEDD** | API/Portal | REQUEST | 3 wks | Weeks 1-3 |
|
||||
| **GMC** | API/Scraper | REQUEST | 1 wk | Weeks 2-3 |
|
||||
| **NMC** | API/Scraper | REQUEST | 1 wk | Weeks 2-3 |
|
||||
| **Companies House** | ✅ API | READY | 2 wks | Weeks 2-4 |
|
||||
| **GOV.UK** | ✅ API | PUBLIC | 3 days | Week 2 |
|
||||
| **ICAEW** | API/Scraper | REQUEST | 2-3 wks | Q2 |
|
||||
| **SRA** | API/Scraper | REQUEST | 2-3 wks | Q2 |
|
||||
| **DBS** | Partner API | VENDOR | 8 wks | Q3 |
|
||||
|
||||
---
|
||||
|
||||
## Pricing Strategy
|
||||
|
||||
```
|
||||
TIER PRICE/MO CUSTOMERS MRR CONTRIB
|
||||
─────────────────────────────────────────────────────
|
||||
Free £0 30% £0
|
||||
Professional £49/mo 40% 1,470 (30 cust)
|
||||
Enterprise £199/mo 20% 2,985 (15 cust)
|
||||
API/Platform £1,000/mo 5% 5,000 (5 cust)
|
||||
|
||||
TOTAL MRR TARGET: £9,455 (50 customers)
|
||||
ANNUALIZED: £113K
|
||||
|
||||
Break-even MRR: £1,833 (24 customers)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics - Phase 1
|
||||
|
||||
| Metric | Target | Owner | Measurement |
|
||||
|---|---|---|---|
|
||||
| Signups | 500+ month 1 | Growth | User registrations |
|
||||
| Active Rate | 10%+ weekly | Product | Weekly check rate |
|
||||
| HEDD Accuracy | >98% | QA | Match rate vs. actual |
|
||||
| Timeline Detection | 85%+ | QA | Gaps/overlaps caught |
|
||||
| Feature Satisfaction | 85%+ | Support | NPS feedback |
|
||||
| Infrastructure Uptime | 99.9% | DevOps | Monitoring alerts |
|
||||
|
||||
---
|
||||
|
||||
## Fraud Detection Coverage
|
||||
|
||||
```
|
||||
FRAUD TYPE DETECTION RATE PHASE
|
||||
───────────────────────────────────────────────────
|
||||
Fake Degrees 90%+ Phase 1 ⭐
|
||||
Job Title Inflation Partial Phase 1
|
||||
Employment Date Lies 80%+ Phase 1 ⭐
|
||||
Directorship False Claims 95%+ Phase 1 ⭐
|
||||
Professional Cert Fraud 95%+ Phase 2
|
||||
Timeline Gaps/Overlaps 85%+ Phase 1 ⭐
|
||||
Medical Register Fraud 95%+ Phase 1 ⭐
|
||||
|
||||
PHASE 1 COVERAGE: ~80% of common fraud patterns
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Financial Projections
|
||||
|
||||
### Year 1 (Conservative)
|
||||
```
|
||||
Customers: 50
|
||||
MRR: £9,455
|
||||
Annual Revenue: £113,460
|
||||
Gross Margin: 75% (£85K)
|
||||
Operating Costs: £220K
|
||||
Result: BREAK-EVEN
|
||||
```
|
||||
|
||||
### Year 1 (Growth)
|
||||
```
|
||||
Customers: 100
|
||||
MRR: £18,910
|
||||
Annual Revenue: £226,920
|
||||
Gross Margin: 75% (£170K)
|
||||
Operating Costs: £220K
|
||||
Result: PROFITABLE (+£20K margin)
|
||||
```
|
||||
|
||||
### CAC Payback
|
||||
```
|
||||
Customer Acquisition Cost: £150-300
|
||||
Average Revenue Per User: £60-120/mo
|
||||
Payback Period: 2-4 MONTHS (healthy)
|
||||
LTV:CAC Ratio: 4:1+ (excellent)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 30-Day Action Plan
|
||||
|
||||
### NOW (This Week)
|
||||
```
|
||||
Priority 1: Email HEDD (partnerships@hedd.ac.uk)
|
||||
↳ Request API access or partnership discussion
|
||||
↳ Mention CV verification for UK recruiters
|
||||
↳ Expect response: 5-10 business days
|
||||
|
||||
Priority 2: Email GMC (digital@gmc-uk.org)
|
||||
↳ Same request structure
|
||||
↳ Fallback: scraper development if denied
|
||||
|
||||
Priority 3: Allocate Engineering Resources
|
||||
↳ 2 FTE engineers full-time for 8 weeks
|
||||
↳ 1 QA engineer (weeks 2-3)
|
||||
↳ 1 PM for coordination
|
||||
|
||||
Priority 4: Recruit Beta Partners
|
||||
↳ Target 3-5 recruitment agencies
|
||||
↳ Offer free access in exchange for feedback
|
||||
↳ Aim: 5+ interviews by end of week 2
|
||||
```
|
||||
|
||||
### WEEK 2-3 (Development Starts)
|
||||
```
|
||||
✅ HEDD credentials received (or scraper ready)
|
||||
✅ Development environment configured
|
||||
✅ Companies House enhancement code started
|
||||
✅ Timeline analysis enhancements begun
|
||||
✅ Healthcare register scrapers drafted
|
||||
```
|
||||
|
||||
### WEEK 4-8 (Beta & Launch)
|
||||
```
|
||||
✅ Features completed & tested
|
||||
✅ Beta deployment & testing
|
||||
✅ Public launch announcement
|
||||
✅ First 100 signups targeted
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Team Requirements
|
||||
|
||||
### Phase 1 (Q1)
|
||||
```
|
||||
Backend Engineer (Lead): Full-time (8 weeks) - HEDD integration
|
||||
Backend Engineer (Secondary): Full-time (8 weeks) - Healthcare + Timeline
|
||||
QA Engineer: Part-time (weeks 2-3)
|
||||
Product Manager: Full-time (coordination)
|
||||
Marketing Lead: Part-time (50%) - Content & outreach
|
||||
```
|
||||
|
||||
### Phase 2 Addition (Q2)
|
||||
```
|
||||
+ Full-Stack Engineer (vertical expansion)
|
||||
+ Sales/BD Lead (partnership development)
|
||||
```
|
||||
|
||||
### Phase 3 Addition (Q3)
|
||||
```
|
||||
+ Customer Success Manager
|
||||
+ Data Analyst (metrics/LTV)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Risk Dashboard
|
||||
|
||||
```
|
||||
RISK PROB SEVERITY MITIGATION
|
||||
──────────────────────────────────────────────────────────
|
||||
HEDD API Access Delayed 🟡 🟡 Scraper fallback
|
||||
GMC Blocks Scraping 🟢 🟡 Request official API
|
||||
Market Adoption Slow 🟡 🔴 Focus healthcare 1st
|
||||
Regulatory Gatekeeping 🟢 🟡 Partner early
|
||||
Competitor Response 🟡 🟡 First-mover advantage
|
||||
|
||||
🟢 LOW (20%) 🟡 MEDIUM (50%) 🔴 HIGH (80%)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Customer Personas
|
||||
|
||||
### Persona 1: Agency Owner (Mid-Market)
|
||||
```
|
||||
Name: Sarah, 48 years old
|
||||
Company: Recruitment agency (80 staff)
|
||||
Problem: Wasting 2-3 hrs/hire verifying degrees
|
||||
Budget: £3K-8K/year on screening
|
||||
Buying Signal: "Can you reduce verification from 5 days to 5 min?"
|
||||
Target Tier: PROFESSIONAL (£49/mo)
|
||||
```
|
||||
|
||||
### Persona 2: Corporate HR Manager
|
||||
```
|
||||
Name: James, 35 years old
|
||||
Company: Financial services (200 employees)
|
||||
Problem: Regulatory liability + reputational risk
|
||||
Budget: £20K-50K/year on compliance
|
||||
Buying Signal: "We need proof every hire is verified"
|
||||
Target Tier: ENTERPRISE (£199/mo)
|
||||
```
|
||||
|
||||
### Persona 3: Healthcare Recruiter
|
||||
```
|
||||
Name: Dr. Lisa, 42 years old
|
||||
Company: Healthcare recruiter (20 staff)
|
||||
Problem: Need instant GMC/NMC verification
|
||||
Budget: £2K-5K/year (cost-sensitive)
|
||||
Buying Signal: "If you verify healthcare pros instantly, we're in"
|
||||
Target Tier: PROFESSIONAL (£49/mo)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Sales Channels
|
||||
|
||||
```
|
||||
1. DIRECT SALES (Primary)
|
||||
└─ Target agency owners + HR directors
|
||||
└─ LinkedIn outreach + cold calls
|
||||
└─ Expected conversion: 5-8%
|
||||
└─ Sales cycle: 2-4 weeks
|
||||
|
||||
2. PARTNERSHIPS (Secondary)
|
||||
└─ ATS integrations (Workable, Bullhorn, Lever)
|
||||
└─ White-label for background check providers
|
||||
└─ Marketplace listings (Zapier, Make)
|
||||
└─ Expected impact: +30% acquisition
|
||||
|
||||
3. CONTENT & SEO (Tertiary)
|
||||
└─ Blog posts: "UK CV Fraud Patterns"
|
||||
└─ Case studies: "How we caught 18 fake degrees"
|
||||
└─ Webinars for HR professionals
|
||||
└─ Expected impact: +20% organic
|
||||
|
||||
4. VERTICAL SPECIALISTS (Niche)
|
||||
└─ Healthcare recruiting (GMC/NMC entry)
|
||||
└─ Financial services (ICAEW/compliance)
|
||||
└─ Legal recruiting (SRA verification)
|
||||
└─ Expected impact: +25% high-value
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Decision Framework
|
||||
|
||||
### Go-Ahead Criteria
|
||||
- ✅ 2 engineers available full-time for 8 weeks
|
||||
- ✅ HEDD partnership or web portal access confirmed
|
||||
- ✅ 3+ beta partner recruitment agencies identified
|
||||
- ✅ Budget approved for Phase 1 (£40-50K estimated)
|
||||
- ✅ Product & GTM strategy aligned with leadership
|
||||
|
||||
### Red Flags (Stop & Reassess)
|
||||
- ❌ HEDD access denied AND scraper approach infeasible
|
||||
- ❌ <3 beta partners willing to participate
|
||||
- ❌ Engineering capacity not available
|
||||
- ❌ Market research shows insufficient demand
|
||||
- ❌ Competitive threat emerges
|
||||
|
||||
---
|
||||
|
||||
## Key Contacts
|
||||
|
||||
| Role | Contact | Email | Notes |
|
||||
|---|---|---|---|
|
||||
| **HEDD Partnership** | Prospects Ltd | partnerships@hedd.ac.uk | START HERE |
|
||||
| **GMC Verification** | GMC Digital Team | digital@gmc-uk.org | Request API access |
|
||||
| **NMC Verification** | NMC Tech Team | [Check website] | Parallel request |
|
||||
| **Companies House** | Already have API | [API Docs available] | No action needed |
|
||||
| **DBS Vendors** | Verifile / DDC | [See API guide] | Q3 partnership |
|
||||
|
||||
---
|
||||
|
||||
## Next Document to Read
|
||||
|
||||
Based on your role:
|
||||
- **Executive:** Read UK_MARKET_STRATEGY.md → Market Opportunity section
|
||||
- **Product:** Read UK_FEATURE_PRIORITIZATION.md (full)
|
||||
- **Engineering:** Read PHASE1_TECHNICAL_IMPLEMENTATION.md (full)
|
||||
- **Everyone:** Read README_UK_STRATEGY.md for navigation
|
||||
|
||||
---
|
||||
|
||||
## One-Liner Summary
|
||||
|
||||
> **TrueCV is the only UK CV verification tool that catches 90% of fake degrees + employment fraud in seconds, leveraging HEDD, GMC/NMC, and Companies House APIs to dominate a £3.3M untapped recruitment market.**
|
||||
|
||||
---
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Status:** Ready for Q1 2026 Execution
|
||||
**Next Review:** Post-Phase 1 Launch (Week 8)
|
||||
|
||||
@@ -1,419 +0,0 @@
|
||||
# TrueCV UK Market Strategy - Complete Package
|
||||
|
||||
## Overview
|
||||
|
||||
This directory contains the complete product strategy and implementation plan for launching TrueCV with a UK-only focus. The documents provide market analysis, feature prioritization, technical implementation details, and go-to-market strategy.
|
||||
|
||||
---
|
||||
|
||||
## Document Hierarchy
|
||||
|
||||
### 1. **EXECUTIVE_SUMMARY.md** ⭐ START HERE
|
||||
**Audience:** Executives, Product Leadership, Investors
|
||||
**Purpose:** 5-minute overview of opportunity, market sizing, and financial projections
|
||||
**Key Sections:**
|
||||
- Market problem (£4.2B annual cost)
|
||||
- Competitive advantage (only player integrating HEDD)
|
||||
- Revenue projections (£113K-226K Year 1)
|
||||
- 30-day action plan
|
||||
|
||||
**Read Time:** 5 minutes
|
||||
|
||||
---
|
||||
|
||||
### 2. **UK_FEATURE_PRIORITIZATION.md**
|
||||
**Audience:** Product Managers, Engineering Leads
|
||||
**Purpose:** Detailed feature prioritization ranking detection value × implementation feasibility
|
||||
**Key Sections:**
|
||||
- Fraud patterns & detection rates
|
||||
- Available UK APIs & accessibility
|
||||
- Ranked feature list (8 features across 3 phases)
|
||||
- Implementation timeline & examples
|
||||
- Success metrics per feature
|
||||
|
||||
**Read Time:** 20-30 minutes
|
||||
|
||||
---
|
||||
|
||||
### 3. **PHASE1_TECHNICAL_IMPLEMENTATION.md**
|
||||
**Audience:** Backend Engineers, QA Engineers
|
||||
**Purpose:** Complete technical specifications for Phase 1 (8-week) delivery
|
||||
**Key Sections:**
|
||||
- Architecture diagrams
|
||||
- 7 complete code examples (interfaces, services, models)
|
||||
- Database schema updates
|
||||
- Test coverage requirements
|
||||
- Configuration & environment setup
|
||||
- Deployment checklist
|
||||
|
||||
**Read Time:** 30-40 minutes (skim for reference)
|
||||
|
||||
---
|
||||
|
||||
### 4. **UK_MARKET_STRATEGY.md**
|
||||
**Audience:** Product Team, Marketing, Sales
|
||||
**Purpose:** Comprehensive market analysis and go-to-market strategy
|
||||
**Key Sections:**
|
||||
- Market sizing (£2.8B UK screening market, £3.3M TrueCV TAM)
|
||||
- Competitive landscape analysis
|
||||
- 3-phase product roadmap (Q1-Q3 2026)
|
||||
- GTM strategy (4 sales channels)
|
||||
- Customer personas (3 types)
|
||||
- Unit economics & financial projections
|
||||
- Long-term vision (2-3 years, international expansion)
|
||||
|
||||
**Read Time:** 30-40 minutes
|
||||
|
||||
---
|
||||
|
||||
### 5. **API_RESOURCES_AND_CONTACTS.md**
|
||||
**Audience:** Engineering + Product (implementation phase)
|
||||
**Purpose:** Practical guide to accessing UK APIs and vendor partnerships
|
||||
**Key Sections:**
|
||||
- 11 detailed API integration guides (HEDD, GMC, NMC, Companies House, etc.)
|
||||
- Contact information for each service
|
||||
- Integration methods (API vs. scraper alternatives)
|
||||
- Timeline for access approval
|
||||
- Vendor recommendations (DBS, HMRC)
|
||||
- Email template for API requests
|
||||
- Compliance checklist
|
||||
|
||||
**Read Time:** 20-30 minutes (reference document)
|
||||
|
||||
---
|
||||
|
||||
## Quick Navigation
|
||||
|
||||
### By Role
|
||||
|
||||
**Executive / Decision-Maker:**
|
||||
1. Start: EXECUTIVE_SUMMARY.md (5 min)
|
||||
2. Then: UK_MARKET_STRATEGY.md - Market & GTM sections (10 min)
|
||||
3. Decision: Approve Phase 1 go-ahead
|
||||
|
||||
**Product Manager:**
|
||||
1. Start: EXECUTIVE_SUMMARY.md (5 min)
|
||||
2. Then: UK_FEATURE_PRIORITIZATION.md (full read)
|
||||
3. Then: UK_MARKET_STRATEGY.md (full read)
|
||||
4. Then: Reference PHASE1_TECHNICAL_IMPLEMENTATION.md & API_RESOURCES_AND_CONTACTS.md as needed
|
||||
|
||||
**Engineering Lead:**
|
||||
1. Start: EXECUTIVE_SUMMARY.md (5 min for context)
|
||||
2. Then: PHASE1_TECHNICAL_IMPLEMENTATION.md (full read)
|
||||
3. Then: API_RESOURCES_AND_CONTACTS.md (full read)
|
||||
4. Reference: UK_FEATURE_PRIORITIZATION.md for feature specs
|
||||
|
||||
**Investor / Analyst:**
|
||||
1. Start: EXECUTIVE_SUMMARY.md
|
||||
2. Then: UK_MARKET_STRATEGY.md - Market size & financials sections
|
||||
3. Reference: UK_FEATURE_PRIORITIZATION.md for technical validation
|
||||
|
||||
**Sales / Marketing:**
|
||||
1. Start: EXECUTIVE_SUMMARY.md
|
||||
2. Then: UK_MARKET_STRATEGY.md (GTM + Personas)
|
||||
3. Reference: UK_FEATURE_PRIORITIZATION.md for talking points
|
||||
|
||||
---
|
||||
|
||||
## Key Metrics at a Glance
|
||||
|
||||
### Market Opportunity
|
||||
- **TAM (Total Addressable Market):** £3.3M
|
||||
- **SAM (Serviceable Available Market):** £200-300K (Year 1)
|
||||
- **Year 1 Revenue Target:** £30-240K (conservative to growth)
|
||||
- **Break-even Customers:** 24-30 paying customers
|
||||
- **Expected Timeline to Break-even:** Month 6-7
|
||||
|
||||
### Phase 1 Deliverables (8 weeks)
|
||||
| Feature | Impact | Effort | Timeline |
|
||||
|---|---|---|---|
|
||||
| HEDD Degree Verification | 9.5/10 | 2-3 weeks | Weeks 1-3 |
|
||||
| Healthcare Register Checks | 6.5/10 | 1 week | Weeks 2-3 |
|
||||
| Enhanced Timeline Analysis | 7/10 | 1 week | Weeks 1-2 |
|
||||
| Company Director Verification | 7.5/10 | 1-2 weeks | Weeks 2-4 |
|
||||
|
||||
### Success Metrics
|
||||
- **Adoption:** 500+ signups in first month
|
||||
- **Engagement:** 10%+ weekly active check rate
|
||||
- **Satisfaction:** 85%+ feature satisfaction (NPS >40)
|
||||
|
||||
---
|
||||
|
||||
## Fraud Detection Coverage
|
||||
|
||||
| Fraud Type | Detection Rate | Phase |
|
||||
|---|---|---|
|
||||
| Fake degrees | 90%+ | Phase 1 |
|
||||
| Employment date falsification | 80%+ | Phase 1 |
|
||||
| Job title inflation | Partial (manual) | Phase 1 |
|
||||
| Exaggerated qualifications | 85%+ | Phase 2 |
|
||||
| Professional registration fraud | 95%+ | Phase 2 |
|
||||
| Directorship false claims | 95%+ | Phase 1 |
|
||||
|
||||
---
|
||||
|
||||
## Competitive Moat
|
||||
|
||||
**Why no competitor offers this:**
|
||||
|
||||
1. **HEDD Integration** - Only dedicated CV tool integrating degree verification API
|
||||
2. **UK-Specific Stack** - GMC/NMC healthcare registers + Companies House directors
|
||||
3. **Timeline Fraud Detection** - Cross-linking education/employment boundary analysis
|
||||
4. **Vertical Focus** - Healthcare recruiting niche dominance (GMC/NMC)
|
||||
5. **First-Mover Advantage** - Market gap unexploited until now
|
||||
|
||||
**Defensibility:**
|
||||
- Deep integrations hard to replicate (HEDD, Companies House, professional bodies)
|
||||
- Network effects once established (more data = better fraud detection)
|
||||
- Compliance/audit trail = switching costs for enterprise customers
|
||||
|
||||
---
|
||||
|
||||
## Critical Path Dependencies
|
||||
|
||||
### Week 1 Actions (Do Now)
|
||||
- [ ] Email HEDD (partnerships@hedd.ac.uk) requesting API access
|
||||
- [ ] Email GMC requesting verification API access
|
||||
- [ ] Allocate 2 engineers to Phase 1 development
|
||||
- [ ] Recruit 3-5 beta partner agencies
|
||||
|
||||
### Week 2-3
|
||||
- [ ] HEDD credential delivery (or scraper fallback)
|
||||
- [ ] Companies House enhancement development starts
|
||||
- [ ] Healthcare register scraper development starts
|
||||
- [ ] Timeline analysis enhancement starts
|
||||
|
||||
### Week 4-8
|
||||
- [ ] Beta testing with agency partners
|
||||
- [ ] Public beta launch
|
||||
- [ ] Feedback iteration
|
||||
- [ ] GA release
|
||||
|
||||
---
|
||||
|
||||
## Financial Summary
|
||||
|
||||
### Year 1 Projections (Conservative Scenario)
|
||||
|
||||
**50 Customers by end of year:**
|
||||
- 30 × Professional tier (£49/mo): £1,470/mo
|
||||
- 15 × Enterprise tier (£199/mo): £2,985/mo
|
||||
- 5 × API/Platform (£1,000/mo): £5,000/mo
|
||||
- **Total MRR (Dec 2026):** £9,455
|
||||
- **Annualized Run Rate:** £113,460
|
||||
|
||||
**Operating Costs:**
|
||||
- Engineering (2 FTE): £150K/year
|
||||
- Infrastructure/APIs: £20K/year
|
||||
- Sales/Marketing: £30K/year
|
||||
- Operations: £20K/year
|
||||
- **Total:** £220K/year
|
||||
|
||||
**Result:** Break-even at 24 customers; profitable at 50+ customers
|
||||
|
||||
### Year 2 Projections (Growth Scenario)
|
||||
|
||||
**150 Customers:**
|
||||
- MRR: £28,000+
|
||||
- Annualized: £336K+
|
||||
- Gross Profit: 75% = £252K+
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
| Risk | Probability | Severity | Mitigation |
|
||||
|---|---|---|---|
|
||||
| HEDD API access delayed | Medium | Medium | Use web portal integration fallback |
|
||||
| GMC/NMC scraper blocked | Low | Low | Request official API proactively |
|
||||
| Market adoption slow | Medium | High | Focus on healthcare vertical first |
|
||||
| Regulatory gatekeeping | Low | Medium | Partner with established vendors early |
|
||||
| Competitive response | Medium | Medium | Maintain first-mover advantage + deepen integrations |
|
||||
|
||||
---
|
||||
|
||||
## Next Steps (30-Day Plan)
|
||||
|
||||
### Days 1-3: Preparation
|
||||
- [ ] Finalize HEDD compliance/consent workflows
|
||||
- [ ] Identify recruitment agency beta partners
|
||||
- [ ] Set up development environment
|
||||
- [ ] Brief engineering team on Phase 1 scope
|
||||
|
||||
### Days 4-7: API Access Requests
|
||||
- [ ] Email HEDD, GMC, NMC with API access requests
|
||||
- [ ] Register on beta platforms where available
|
||||
- [ ] Prepare scraper fallbacks for development
|
||||
|
||||
### Days 8-21: Development (Parallel Tracks)
|
||||
- **Track 1:** HEDD integration (lead engineer)
|
||||
- **Track 2:** Healthcare registers (secondary engineer)
|
||||
- **Track 3:** Companies House enhancement (tertiary)
|
||||
- **Track 4:** Timeline enhancement (parallel)
|
||||
|
||||
### Days 22-28: Beta Testing
|
||||
- [ ] Deploy to test environment
|
||||
- [ ] Onboard beta partner agencies
|
||||
- [ ] Collect feedback on UX/value
|
||||
- [ ] Iterate on flag messaging
|
||||
|
||||
### Days 29-30: Preparation for Public Launch
|
||||
- [ ] Final testing & QA approval
|
||||
- [ ] Marketing assets prepared
|
||||
- [ ] Pricing finalized
|
||||
- [ ] Customer support workflows documented
|
||||
|
||||
---
|
||||
|
||||
## Recommended Reading Order
|
||||
|
||||
**For executives/investors (20 minutes total):**
|
||||
1. EXECUTIVE_SUMMARY.md (5 min)
|
||||
2. UK_MARKET_STRATEGY.md → Market Opportunity + Financials sections (10 min)
|
||||
3. UK_FEATURE_PRIORITIZATION.md → Overview + Ranked Feature List (5 min)
|
||||
|
||||
**For product/engineering teams (90 minutes total):**
|
||||
1. EXECUTIVE_SUMMARY.md (5 min)
|
||||
2. UK_FEATURE_PRIORITIZATION.md (30 min - full read)
|
||||
3. PHASE1_TECHNICAL_IMPLEMENTATION.md (40 min - full read)
|
||||
4. API_RESOURCES_AND_CONTACTS.md (15 min - reference)
|
||||
|
||||
**For detailed implementation (3-4 hours for engineers):**
|
||||
1. PHASE1_TECHNICAL_IMPLEMENTATION.md (40 min - detailed read)
|
||||
2. API_RESOURCES_AND_CONTACTS.md (30 min - full read)
|
||||
3. Code examples in PHASE1_TECHNICAL_IMPLEMENTATION.md (60-90 min - study)
|
||||
4. UK_FEATURE_PRIORITIZATION.md → Implementation Examples (30 min)
|
||||
|
||||
---
|
||||
|
||||
## Questions & Decision Points
|
||||
|
||||
### For Product Leadership
|
||||
1. **Commitment:** Can we allocate 2 engineers full-time for 8 weeks?
|
||||
2. **Partnerships:** Will we sponsor API access requests to HEDD, GMC, NMC?
|
||||
3. **Revenue Model:** Approve tiered pricing (Free/Pro/Enterprise)?
|
||||
4. **Timeline:** Can we launch beta by Week 5?
|
||||
5. **International:** After UK success, should we expand to Ireland/Australia?
|
||||
|
||||
### For Engineering
|
||||
1. **Resources:** Do we have backend capacity starting immediately?
|
||||
2. **Technical:** Any concerns with HEDD/GMC/NMC integration approach?
|
||||
3. **Alternatives:** Any preference on API vs. scraper approach for health registers?
|
||||
4. **Testing:** Do we have QA capacity for Phase 1 scope?
|
||||
|
||||
### For Sales/Marketing
|
||||
1. **Positioning:** Are we confident "degree verification" is primary differentiator?
|
||||
2. **Channels:** Should we prioritize direct sales or partnerships?
|
||||
3. **Pricing:** Does £49/Professional tier feel right for target market?
|
||||
4. **Beta:** Can we identify 3-5 recruitment agency beta partners by Week 2?
|
||||
|
||||
---
|
||||
|
||||
## Success Criteria (End of Phase 1)
|
||||
|
||||
### Must-Haves
|
||||
- ✅ HEDD integration live (real-time + manual review tracking)
|
||||
- ✅ Timeline fraud detection enhanced
|
||||
- ✅ Companies House director verification working
|
||||
- ✅ GMC/NMC healthcare checks live
|
||||
- ✅ 500+ public signups
|
||||
- ✅ 10%+ weekly active check rate
|
||||
|
||||
### Nice-to-Haves
|
||||
- ✅ 85%+ user satisfaction score
|
||||
- ✅ Media/analyst coverage
|
||||
- ✅ 5+ paying customers (revenue: £2-5K MRR)
|
||||
- ✅ Documented case studies
|
||||
|
||||
### Failure Indicators (Red Flags)
|
||||
- ❌ <100 signups after public launch
|
||||
- ❌ HEDD/GMC access denied with no scraper backup
|
||||
- ❌ >5% API error rate
|
||||
- ❌ <50% feature adoption
|
||||
|
||||
---
|
||||
|
||||
## File Manifest
|
||||
|
||||
```
|
||||
/mnt/d/Git/TrueCV/
|
||||
├── EXECUTIVE_SUMMARY.md (5-page exec overview)
|
||||
├── UK_FEATURE_PRIORITIZATION.md (30-page detailed prioritization)
|
||||
├── PHASE1_TECHNICAL_IMPLEMENTATION.md (60-page technical specs + code)
|
||||
├── UK_MARKET_STRATEGY.md (40-page market + GTM strategy)
|
||||
├── API_RESOURCES_AND_CONTACTS.md (20-page API integration guide)
|
||||
└── README_UK_STRATEGY.md (this file - navigation guide)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Version History
|
||||
|
||||
**v1.0 - January 2026**
|
||||
- Initial UK market strategy & implementation plan
|
||||
- 4 comprehensive strategy documents
|
||||
- Phase 1 technical specifications
|
||||
- API integration guide
|
||||
- Ready for Q1 2026 execution
|
||||
|
||||
---
|
||||
|
||||
## Contact & Support
|
||||
|
||||
**For questions about this strategy:**
|
||||
- Product Lead: [Name] - Overall strategy & market analysis
|
||||
- Engineering Lead: [Name] - Technical feasibility & architecture
|
||||
- API Integration: [Name] - HEDD, GMC, NMC coordination
|
||||
|
||||
**For strategy updates:**
|
||||
These documents will be updated quarterly with:
|
||||
- Market feedback from beta partners
|
||||
- API integration progress
|
||||
- Competitive landscape changes
|
||||
- Revised financial projections
|
||||
- Phase 2 feature updates
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Key Definitions
|
||||
|
||||
**HEDD:** Higher Education Degree Datacheck - UK's official degree verification service (140+ universities)
|
||||
|
||||
**GMC:** General Medical Council - UK regulator for doctors (~250K registered)
|
||||
|
||||
**NMC:** Nursing and Midwifery Council - UK regulator for nurses/midwives (~700K registered)
|
||||
|
||||
**Companies House:** UK company registration authority - maintains register of 3.4M companies + directors
|
||||
|
||||
**Timeline Fraud:** Employment/education date inconsistencies (overlaps, gaps, sequential issues)
|
||||
|
||||
**TAM:** Total Addressable Market - theoretical maximum revenue if 100% market capture
|
||||
|
||||
**SAM:** Serviceable Available Market - realistic segment we can target
|
||||
|
||||
**SOM:** Serviceable Obtainable Market - Year 1 revenue target
|
||||
|
||||
**CAC:** Customer Acquisition Cost - avg. cost to acquire one paying customer
|
||||
|
||||
**ARPU:** Average Revenue Per User - avg. monthly/annual revenue per customer
|
||||
|
||||
**LTV:CAC:** Customer lifetime value to acquisition cost ratio (healthy SaaS: >3:1)
|
||||
|
||||
---
|
||||
|
||||
## License & Confidentiality
|
||||
|
||||
This strategy document is internal to TrueCV and contains commercially sensitive information including:
|
||||
- Market sizing & financial projections
|
||||
- Competitive positioning
|
||||
- Product roadmap
|
||||
- API integration details
|
||||
|
||||
**Distribution:** Product team, engineering, leadership only
|
||||
|
||||
---
|
||||
|
||||
END OF DOCUMENT
|
||||
|
||||
**Last Updated:** January 20, 2026
|
||||
**Next Review:** April 1, 2026 (Post-Phase 1 Launch)
|
||||
@@ -5,17 +5,17 @@ VisualStudioVersion = 17.0.31903.59
|
||||
MinimumVisualStudioVersion = 10.0.40219.1
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F25C3740-9240-46DF-BC34-985BC577216B}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCV.Domain", "src\TrueCV.Domain\TrueCV.Domain.csproj", "{41AC48AF-09BC-48D1-9CA4-1B05D3B693F0}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealCV.Domain", "src\RealCV.Domain\RealCV.Domain.csproj", "{41AC48AF-09BC-48D1-9CA4-1B05D3B693F0}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCV.Application", "src\TrueCV.Application\TrueCV.Application.csproj", "{A8A1BA81-3B2F-4F95-BB15-ACA40DF2A70E}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealCV.Application", "src\RealCV.Application\RealCV.Application.csproj", "{A8A1BA81-3B2F-4F95-BB15-ACA40DF2A70E}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCV.Infrastructure", "src\TrueCV.Infrastructure\TrueCV.Infrastructure.csproj", "{03DB607C-9592-4930-8C89-3E257A319278}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealCV.Infrastructure", "src\RealCV.Infrastructure\RealCV.Infrastructure.csproj", "{03DB607C-9592-4930-8C89-3E257A319278}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCV.Web", "src\TrueCV.Web\TrueCV.Web.csproj", "{D69F57DB-3092-48AF-81BB-868E3749C638}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealCV.Web", "src\RealCV.Web\RealCV.Web.csproj", "{D69F57DB-3092-48AF-81BB-868E3749C638}"
|
||||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{80890010-EDA6-418B-AD6C-5A9D875594C4}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrueCV.Tests", "tests\TrueCV.Tests\TrueCV.Tests.csproj", "{4450D4F1-4EB9-445E-904B-1C57701493D8}"
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RealCV.Tests", "tests\RealCV.Tests\RealCV.Tests.csproj", "{4450D4F1-4EB9-445E-904B-1C57701493D8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
@@ -1,631 +0,0 @@
|
||||
# TrueCV UK Market Feature Prioritization
|
||||
|
||||
**Date:** January 2026
|
||||
**Focus:** UK-Only Market Opportunities
|
||||
**Baseline:** Companies House integration, Claude AI parsing, Timeline analysis
|
||||
|
||||
---
|
||||
|
||||
## Executive Summary
|
||||
|
||||
UK CV fraud is escalating with AI-generated deepfakes, synthetic identities, and traditional qualification falsification. The most impactful opportunity for TrueCV in the UK market is **degree verification integration** (HEDD API), followed by **employment verification automation** and **professional body registration checks**. These three features represent 78% of recruiter pain points and address 85% of detected fraud patterns.
|
||||
|
||||
---
|
||||
|
||||
## Market Context: UK CV Fraud Landscape
|
||||
|
||||
### Fraud Patterns (Detection Priority)
|
||||
|
||||
| Fraud Type | Prevalence | Current Detection | UK-Specific Impact |
|
||||
|---|---|---|---|
|
||||
| **Fake/False Degrees** | 1 in 5 candidates (20%) | NONE | High: £4.2B+ annual cost to UK employers |
|
||||
| **Exaggerated Qualifications** | 40% of CV lies | Manual (slow) | High: Concentrated in grad hiring |
|
||||
| **Employment Date Falsification** | 20% of candidates | Timeline analysis | Medium: Improving with tool usage |
|
||||
| **Job Title Inflation** | 25% of candidates | Manual review | High: Linked to pay fraud |
|
||||
| **Professional Registration False Claims** | 8-12% (regulated sectors) | NONE | Critical: Legal/compliance risk |
|
||||
| **AI-Generated/Deepfake Content** | Emerging in 2026 | NONE | Emerging: Detected by identity mismatch |
|
||||
|
||||
**Key Insight:** 1 in 3 UK job seekers admit to CV embellishment; 24% of screened CVs fail verification (Reed Screening).
|
||||
|
||||
---
|
||||
|
||||
## Available UK Data Sources & APIs
|
||||
|
||||
### 1. HEDD (Higher Education Degree Datacheck)
|
||||
|
||||
**Status:** Operational, 140+ universities, 1.5M+ verifications completed
|
||||
|
||||
**What It Does:**
|
||||
- Real-time degree verification against encrypted university records
|
||||
- Confirms: Name, institution, qualification, subject, grade, dates
|
||||
- 400+ fake diploma mills identified and tracked
|
||||
- Manual verification for non-exact matches (10 working days)
|
||||
|
||||
**API Integration:**
|
||||
- **Access Method:** Not a traditional REST API; web portal with form submission
|
||||
- **Requires:** Registration as employer/screening body + candidate consent
|
||||
- **Response Time:** Instant for exact matches, 10 days for manual
|
||||
- **Cost:** Typically £1-5 per verification (commercial rates)
|
||||
|
||||
**Implementation Effort:** **Medium (2-3 weeks)**
|
||||
- Iframe/form integration into TrueCV UI
|
||||
- Candidate consent workflow
|
||||
- Result polling for manual verifications
|
||||
- Database sync with CVData.Education entries
|
||||
|
||||
**Impact Score:** **9.5/10**
|
||||
- Eliminates 90%+ of fake degree claims
|
||||
- 1 in 5 UK hires have false degree (Cifas data)
|
||||
- Recruiters rank this #1 missing feature
|
||||
- Regulatory confidence (compliance visible)
|
||||
|
||||
---
|
||||
|
||||
### 2. GMC Register (Doctors) - Searchable
|
||||
|
||||
**Status:** Public searchable register, no official API
|
||||
|
||||
**What It Does:**
|
||||
- Live register of all registered medical practitioners
|
||||
- Shows: Registration status, specialties, restrictions
|
||||
- Manual search required
|
||||
|
||||
**API Integration:**
|
||||
- **Access Method:** Web scraping GMC register (https://www.gmc-uk.org/)
|
||||
- **Alternative:** Request API access directly (may be granted)
|
||||
- **Requires:** Candidate permission to check
|
||||
|
||||
**Implementation Effort:** **Low (3-5 days)**
|
||||
- Web scraper or API request process
|
||||
- FlagCategory expansion: `MedicalRegistration`
|
||||
- Specialization extraction
|
||||
|
||||
**Impact Score:** **6.5/10**
|
||||
- Targets 1.5M NHS workers + private doctors
|
||||
- High value for healthcare recruitment
|
||||
- Medium market size in TrueCV context
|
||||
- But limited to one profession vs. broad application
|
||||
|
||||
---
|
||||
|
||||
### 3. NMC Register (Nurses/Midwives) - Searchable
|
||||
|
||||
**Status:** Public searchable register, no official API
|
||||
|
||||
**What It Does:**
|
||||
- Register of all UK nurses, midwives, nursing associates
|
||||
- Shows: Registration status, Pin number, areas of practice
|
||||
- Real-time updates
|
||||
|
||||
**API Integration:**
|
||||
- **Access Method:** Web scraping NMC register
|
||||
- **Alternative:** Similar API request potential as GMC
|
||||
- **Requires:** Candidate permission
|
||||
|
||||
**Implementation Effort:** **Low (3-5 days)**
|
||||
- Reusable scraper pattern from GMC
|
||||
- FlagCategory expansion: `HealthcareRegistration`
|
||||
|
||||
**Impact Score:** **7/10**
|
||||
- Targets 700K+ UK nurses
|
||||
- Growing market (NHS recruitment surge)
|
||||
- Similar process to GMC
|
||||
- High fraud risk in agency nursing
|
||||
|
||||
---
|
||||
|
||||
### 4. Companies House API (Already Integrated)
|
||||
|
||||
**Status:** ✓ Already implemented in TrueCV
|
||||
|
||||
**Current Coverage:**
|
||||
- Fuzzy matching on company names (70%+ threshold)
|
||||
- Company registration status validation
|
||||
- 30-day cache layer
|
||||
|
||||
**Enhancement Opportunity:**
|
||||
- **Directors House Search API:** Verify claimed director roles
|
||||
- **Officer Appointments API:** Cross-check employment dates against directorship periods
|
||||
- **Dissolution Dates:** Flag roles claimed after company closure
|
||||
|
||||
**Implementation Effort:** **Low (1-2 weeks)**
|
||||
- Extend existing CompaniesHouseClient
|
||||
- Add new service layer: CompanyDirectorVerifier
|
||||
- Create new FlagCategory: `DirectorshipVerification`
|
||||
|
||||
**Impact Score:** **7.5/10**
|
||||
- Validates self-employed/director claims (high fraud area)
|
||||
- Existing infrastructure (quick win)
|
||||
- Medium-high detection value
|
||||
- Applicable to 15-20% of CVs with self-employment
|
||||
|
||||
---
|
||||
|
||||
### 5. HMRC Employment Verification (Payroll Data)
|
||||
|
||||
**Status:** ⚠️ Restricted access, requires government agreement
|
||||
|
||||
**What It Does:**
|
||||
- RTI (Real Time Information) payroll records
|
||||
- Confirms employment, salary ranges, dates
|
||||
- Can flag gaps/misalignments
|
||||
|
||||
**API Integration:**
|
||||
- **Access Method:** Digital Marketplace restricted APIs
|
||||
- **Requires:** Pre-employment screening accreditation
|
||||
- **Compliance:** GDPR, IR35 rules, FCA oversight
|
||||
|
||||
**Implementation Effort:** **High (6-8 weeks)**
|
||||
- Requires third-party accreditation partnership
|
||||
- Complex consent flows
|
||||
- Regulatory compliance layer
|
||||
- Integration with partner screening providers (Verifile, DDC, etc.)
|
||||
|
||||
**Impact Score:** **9/10** (if accessible)
|
||||
- Authoritative employment verification
|
||||
- Detects date falsification with 95%+ accuracy
|
||||
- High compliance value (IR35, tax verification)
|
||||
- BUT: Access requires government partnership
|
||||
|
||||
---
|
||||
|
||||
### 6. Professional Body Registers
|
||||
|
||||
#### Regulated Professions (UK Regulatory Bodies)
|
||||
|
||||
| Profession | Regulator | Register | API Status | Verification Value |
|
||||
|---|---|---|---|---|
|
||||
| Accountants (ICAEW) | ICAEW | Member search | ❌ No API | High (~180K members) |
|
||||
| Lawyers (SRA) | SRA | Public register | ❌ No API | High (~170K solicitors) |
|
||||
| Engineers (IET/ICE) | Various | Member search | ❌ No API | Medium (~150K) |
|
||||
| Architects | RIBA | Public register | ❌ No API | Medium (~50K) |
|
||||
| Psychologists | HCPC | Public register | ❌ No API | Low (~50K) |
|
||||
|
||||
**Access Pattern:** All require manual web scraping or direct API requests to individual bodies
|
||||
|
||||
**Implementation Effort:** **Medium-High (4-6 weeks per profession)**
|
||||
- Build scraper templates per register format
|
||||
- Create generic ProfessionalRegistration flag type
|
||||
- Maintain updatable registry of professions/URLs
|
||||
|
||||
**Impact Score:** **6-7/10** (varies by profession)
|
||||
- ICAEW/SRA highest value (financial/legal fraud common)
|
||||
- Medium-term value; low adoption initially
|
||||
- Regulatory compliance appeal
|
||||
- Requires consent management per profession
|
||||
|
||||
---
|
||||
|
||||
### 7. Regulated Professions Register (GOV.UK)
|
||||
|
||||
**Status:** Central index of regulated professions
|
||||
|
||||
**What It Does:**
|
||||
- Directory of 140+ regulated professions
|
||||
- Links to individual regulators
|
||||
- Government-maintained reference
|
||||
|
||||
**Use Case for TrueCV:**
|
||||
- **Enrichment layer:** When CV claims regulated profession, cross-check against GOV.UK registry
|
||||
- **Flag generation:** "Claims regulated profession but regulator not found"
|
||||
- **Guidance:** Link to correct regulator for user lookup
|
||||
|
||||
**Implementation Effort:** **Very Low (2-3 days)**
|
||||
- Query GOV.UK API or static dataset
|
||||
- Regex match against CV claims
|
||||
- Decision tree for flagging
|
||||
|
||||
**Impact Score:** **5/10**
|
||||
- Low direct detection value
|
||||
- High utility for user education
|
||||
- Low implementation cost
|
||||
- Good for Trust/Transparency (UX win)
|
||||
|
||||
---
|
||||
|
||||
### 8. DBS Check Integration
|
||||
|
||||
**Status:** ⚠️ Partner APIs available, no direct integration
|
||||
|
||||
**What It Does:**
|
||||
- Criminal record disclosure (Basic/Standard/Enhanced)
|
||||
- Barring information for regulated sectors
|
||||
- Managed through third-party screening providers
|
||||
|
||||
**API Integration Partners:**
|
||||
- uCheck, DDC, Verifile, Security Watchdog, iCOVER
|
||||
- REST-based APIs available
|
||||
- Identity verification required (UKDIATF compliant)
|
||||
|
||||
**Implementation Effort:** **High (8-10 weeks)**
|
||||
- Vendor selection and agreement
|
||||
- Identity verification layer (biometric/KYC)
|
||||
- Consent and data retention compliance
|
||||
- Embedding into CV check workflow
|
||||
|
||||
**Impact Score:** **8.5/10** (High business value, regulatory)
|
||||
- Addresses emerging security concern
|
||||
- High compliance requirement for regulated roles
|
||||
- Revenue opportunity (typically £20-50/check)
|
||||
- BUT: Complex compliance, may cannibalize revenue if free tier
|
||||
|
||||
---
|
||||
|
||||
## Ranked Feature Prioritization
|
||||
|
||||
### Priority Matrix: Detection Value × Implementation Feasibility
|
||||
|
||||
```
|
||||
HIGH VALUE + EASY │ HIGH VALUE + HARD
|
||||
─────────────────────┼─────────────────
|
||||
1. HEDD (Degrees) │ 8. DBS Integration
|
||||
2. Timeline Enhance │ 5. HMRC Payroll
|
||||
3. GMC/NMC Scraper │ 6. Professional Bodies
|
||||
4. Directors House │
|
||||
─────────────────────┼─────────────────
|
||||
MEDIUM VALUE + EASY │ MEDIUM VALUE + HARD
|
||||
─────────────────────┼─────────────────
|
||||
7. GOV.UK Registry │
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Recommended Implementation Roadmap
|
||||
|
||||
### Phase 1: Q1 2026 (Weeks 1-8) - High-Impact Foundation
|
||||
|
||||
**1. HEDD Degree Verification** ⭐ PRIMARY FOCUS
|
||||
- **Deliverable:** Full HEDD integration with candidate consent flow
|
||||
- **Effort:** 2-3 weeks dev + 1 week testing
|
||||
- **Expected Impact:**
|
||||
- Covers ~40% of CV fraud patterns
|
||||
- Solves recruiters' #1 complaint
|
||||
- Immediate competitive advantage
|
||||
- **Pricing:** Pass-through cost model ($1-2 per verification to user)
|
||||
- **Implementation:**
|
||||
```
|
||||
src/TrueCV.Infrastructure/ExternalApis/HeddClient.cs
|
||||
src/TrueCV.Application/Interfaces/IEducationVerifierService.cs
|
||||
src/TrueCV.Infrastructure/Services/EducationVerifierService.cs
|
||||
FlagCategory += EducationVerification
|
||||
Add new flag types:
|
||||
- DegreeNotFound
|
||||
- DegreeClassificationMismatch
|
||||
- GraduationDateMismatch
|
||||
- InstitutionNotFound
|
||||
```
|
||||
|
||||
**2. Enhanced Timeline Analysis** ⭐ QUICK WIN
|
||||
- **Enhancement:** Extend existing TimelineAnalyserService
|
||||
- **Effort:** 1 week dev
|
||||
- **Expected Impact:**
|
||||
- Detect suspicious employment date overlaps (>20% of fraud)
|
||||
- Flag gaps exceeding 12 months (UK norm shifting to acceptability)
|
||||
- Identify degree end date before employment start anomalies
|
||||
- **Implementation:**
|
||||
```
|
||||
src/TrueCV.Infrastructure/Services/TimelineAnalyserService.cs
|
||||
- Add: UKEmploymentPatternAnalyzer
|
||||
- Add: EducationEmploymentSequenceValidator
|
||||
- New flags:
|
||||
- EmploymentStartBeforeEducationCompletion
|
||||
- UnusualEmploymentGapPattern
|
||||
- MultipleParallelEmployments (>20% tolerated)
|
||||
```
|
||||
|
||||
**3. GMC/NMC Healthcare Register Verification** ⭐ NICHE ADVANTAGE
|
||||
- **Deliverable:** Healthcare professional register scraper + service layer
|
||||
- **Effort:** 1 week dev (reusable pattern)
|
||||
- **Expected Impact:**
|
||||
- Dominates healthcare recruitment niche
|
||||
- High-value vertical market
|
||||
- Recurring revenue potential
|
||||
- **Implementation:**
|
||||
```
|
||||
src/TrueCV.Infrastructure/ExternalApis/HealthcareRegisterClient.cs
|
||||
src/TrueCV.Application/Interfaces/IHealthcareVerifierService.cs
|
||||
FlagCategory += HealthcareRegistration
|
||||
New flags:
|
||||
- GMCNotFound / GMCRestricted / GMCLapsed
|
||||
- NMCNotFound / NMCRestricted
|
||||
```
|
||||
|
||||
**4. Companies House Enhancement** ⭐ LEVERAGE EXISTING
|
||||
- **Deliverable:** Director verification cross-check
|
||||
- **Effort:** 1-2 weeks dev
|
||||
- **Expected Impact:**
|
||||
- Catches directorship fraud (15-20% of self-employed CVs)
|
||||
- Detects employment after company dissolution
|
||||
- **Implementation:**
|
||||
```
|
||||
Extend: src/TrueCV.Infrastructure/ExternalApis/CompaniesHouseClient.cs
|
||||
Add: OfficerAppointmentsClient.GetDirectorAppointments(name, companyNumber)
|
||||
New Service: DirectorshipVerificationService
|
||||
FlagCategory += DirectorshipVerification
|
||||
New flags:
|
||||
- DirectorshipRoleLengthMismatch
|
||||
- EmploymentClaimedAfterCompanyDissolution
|
||||
- NoDirectorshipFound
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Q2 2026 (Weeks 9-16) - Regulatory & Professional Bodies
|
||||
|
||||
**5. Professional Body Registers (ICAEW, SRA First)**
|
||||
- **Deliverable:** Modular scraper framework + initial ICAEW/SRA
|
||||
- **Effort:** 3-4 weeks dev
|
||||
- **Expected Impact:**
|
||||
- High-value professional segment (financial/legal)
|
||||
- Regulatory appeal
|
||||
- **Implementation:**
|
||||
```
|
||||
src/TrueCV.Infrastructure/ExternalApis/ProfessionalBodyClient.cs
|
||||
src/TrueCV.Infrastructure/ExternalApis/Scrapers/
|
||||
- ICAEWMembershipVerifier.cs
|
||||
- SRALawverVerifier.cs
|
||||
- IETEngineerVerifier.cs
|
||||
FlagCategory += ProfessionalRegistration
|
||||
```
|
||||
|
||||
**6. GOV.UK Regulated Professions Registry**
|
||||
- **Deliverable:** Enrichment layer for professional claims
|
||||
- **Effort:** 2-3 days dev
|
||||
- **Expected Impact:**
|
||||
- Trust/transparency feature
|
||||
- User education value
|
||||
- Low dev cost, medium UX value
|
||||
|
||||
---
|
||||
|
||||
### Phase 3: Q3 2026+ (Strategic Partnerships)
|
||||
|
||||
**7. HMRC RTI Payroll Integration**
|
||||
- **Status:** Requires government partnership/accreditation
|
||||
- **Effort:** 8-10 weeks (vendor dependent)
|
||||
- **Expected Impact:** "Gold standard" employment verification
|
||||
- **Business Model:** Premium feature tier
|
||||
|
||||
**8. DBS Check Partnership**
|
||||
- **Status:** Requires vendor agreement + compliance framework
|
||||
- **Effort:** 8-10 weeks
|
||||
- **Expected Impact:** Security compliance selling point
|
||||
- **Business Model:** Premium tier or per-check revenue
|
||||
|
||||
---
|
||||
|
||||
## Implementation Examples
|
||||
|
||||
### 1. HEDD Integration Example
|
||||
|
||||
```csharp
|
||||
// New service interface
|
||||
public interface IEducationVerifierService
|
||||
{
|
||||
Task<EducationVerificationResult> VerifyDegreeAsync(
|
||||
string candidateName,
|
||||
DateOnly dateOfBirth,
|
||||
string institution,
|
||||
DateOnly? graduationYear,
|
||||
string? qualification,
|
||||
string? subject,
|
||||
string? grade);
|
||||
}
|
||||
|
||||
// New flag categories
|
||||
public enum FlagCategory
|
||||
{
|
||||
Employment,
|
||||
Education, // ✓ Existing
|
||||
Timeline,
|
||||
Plausibility,
|
||||
EducationVerification, // NEW
|
||||
DirectorshipVerification, // NEW
|
||||
HealthcareRegistration, // NEW
|
||||
ProfessionalRegistration // NEW
|
||||
}
|
||||
|
||||
// Example: Enhanced timeline analysis
|
||||
public class TimelineAnalyserService
|
||||
{
|
||||
private const int NormalGapMonths = 3; // UK norm
|
||||
private const int RedFlagGapMonths = 12;
|
||||
|
||||
public TimelineGap CheckGapPlausibility(DateOnly startDate, DateOnly endDate)
|
||||
{
|
||||
if ((endDate - startDate).Days > 366 &&
|
||||
endDate.AddMonths(-NormalGapMonths) < startDate)
|
||||
{
|
||||
return new TimelineGap
|
||||
{
|
||||
Severity = FlagSeverity.Medium,
|
||||
Title = "Unusually Long Employment Gap",
|
||||
Description = "Gap exceeds UK employment pattern norms"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 2. Healthcare Register Scraper Example
|
||||
|
||||
```csharp
|
||||
public class GMCRegisterVerifier
|
||||
{
|
||||
private const string GMCRegisterUrl = "https://www.gmc-uk.org/";
|
||||
|
||||
public async Task<GMCVerificationResult> VerifyDoctorAsync(
|
||||
string fullName,
|
||||
string gmcNumber = null)
|
||||
{
|
||||
// Web scrape or API query GMC register
|
||||
var result = await ScrapeGMCRegisterAsync(fullName, gmcNumber);
|
||||
|
||||
return new GMCVerificationResult
|
||||
{
|
||||
IsFound = result != null,
|
||||
RegistrationStatus = result?.Status,
|
||||
Specialties = result?.Specialties,
|
||||
Restrictions = result?.Restrictions,
|
||||
VerificationConfidence = result != null ? 95 : 0
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
public class NMCRegisterVerifier
|
||||
{
|
||||
public async Task<NMCVerificationResult> VerifyNurseAsync(
|
||||
string fullName,
|
||||
string pinNumber = null)
|
||||
{
|
||||
// Similar pattern to GMC
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3. Companies House Director Verification Example
|
||||
|
||||
```csharp
|
||||
public class DirectorshipVerificationService
|
||||
{
|
||||
public async Task<DirectorshipVerificationResult> VerifyDirectorshipAsync(
|
||||
string candidateName,
|
||||
string companyName,
|
||||
DateOnly claimedStartDate,
|
||||
DateOnly claimedEndDate)
|
||||
{
|
||||
// Get company number from existing Companies House integration
|
||||
var company = await _companyVerifier.VerifyCompanyAsync(companyName);
|
||||
|
||||
if (!company.IsVerified)
|
||||
{
|
||||
return CreateUnverifiedResult("Company not found");
|
||||
}
|
||||
|
||||
// Query officer appointments
|
||||
var appointments = await _companiesHouseClient.GetOfficerAppointmentsAsync(
|
||||
company.MatchedCompanyNumber);
|
||||
|
||||
var matchingAppointment = appointments
|
||||
.FirstOrDefault(a => FuzzyMatch(a.OfficerName, candidateName));
|
||||
|
||||
if (matchingAppointment == null)
|
||||
{
|
||||
return CreateFlagResult(
|
||||
"DirectorshipNotFound",
|
||||
$"No officer appointment found for {candidateName}");
|
||||
}
|
||||
|
||||
// Verify dates align
|
||||
if (matchingAppointment.AppointmentDate > claimedStartDate)
|
||||
{
|
||||
return CreateFlagResult(
|
||||
"DirectorshipDateMismatch",
|
||||
$"Claimed start date ({claimedStartDate}) before appointment date");
|
||||
}
|
||||
|
||||
return CreateVerifiedResult(matchingAppointment);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics for Phase 1
|
||||
|
||||
| Metric | Target | Owner |
|
||||
|---|---|---|
|
||||
| HEDD Integration Live | Week 3 | Engineering |
|
||||
| Education Flags Accuracy | >95% precision | QA |
|
||||
| Timeline Gaps Detected | >80% of actual gaps | Analytics |
|
||||
| GMC/NMC Scraper Complete | Week 4 | Engineering |
|
||||
| Healthcare Niche Adoption | 5+ healthcare recruiter orgs | Sales |
|
||||
| Detection Rate Improvement | +35% over baseline | Product |
|
||||
| User Satisfaction (HEDD) | >85% (low friction) | Support |
|
||||
|
||||
---
|
||||
|
||||
## Risk Mitigation
|
||||
|
||||
### HEDD Integration Risks
|
||||
- **Risk:** API changes or rate limiting
|
||||
- **Mitigation:** Use web portal integration first, request official API later; cache results aggressively
|
||||
- **Risk:** Candidate consent complexity
|
||||
- **Mitigation:** Clear one-click consent flow; educational messaging
|
||||
|
||||
### Professional Register Scraping Risks
|
||||
- **Risk:** Website structure changes break scrapers
|
||||
- **Mitigation:** Robust error handling; monitoring alerts; manual fallback links provided to users
|
||||
- **Risk:** Regulators restrict scraping
|
||||
- **Mitigation:** Request official API access proactively; provide value-add (fraud detection = mutual benefit)
|
||||
|
||||
### HMRC/DBS Integration Risks
|
||||
- **Risk:** Regulatory gatekeeping / approval delays
|
||||
- **Mitigation:** Start vendor conversations NOW; build partnerships in parallel
|
||||
- **Risk:** Compliance burden
|
||||
- **Mitigation:** Partner with established pre-employment screening vendors (Verifile, DDC) who handle compliance
|
||||
|
||||
---
|
||||
|
||||
## Competitive Advantage Summary
|
||||
|
||||
| Feature | TrueCV Advantage | Timeline |
|
||||
|---|---|---|
|
||||
| **HEDD Integration** | Only dedicated CV tool with instant degree verification | Q1 2026 |
|
||||
| **Healthcare Register Targeting** | Only tool targeting healthcare recruitment niche | Q1 2026 |
|
||||
| **Timeline + Education Linking** | CV tells employment started before degree completed = RED FLAG | Q1 2026 |
|
||||
| **Professional Body Framework** | Modular; expandable to 140+ professions vs competitors' static lists | Q2 2026 |
|
||||
| **Companies House Directors** | Only tool verifying self-employment claims against official records | Q1 2026 |
|
||||
|
||||
---
|
||||
|
||||
## UK Market Positioning
|
||||
|
||||
**Tagline:** *"The only CV verification tool UK recruiters need - from degree to directorship"*
|
||||
|
||||
**Market Segment:** Recruitment agencies, HR departments, background screening companies
|
||||
|
||||
**Price Model (Suggested):**
|
||||
- **Free Tier:** Companies House + Timeline Analysis
|
||||
- **Professional Tier:** +HEDD verification, +Healthcare registers (£29-49/user/month)
|
||||
- **Enterprise Tier:** +HMRC payroll, +DBS integration, +Professional bodies (Custom pricing)
|
||||
|
||||
---
|
||||
|
||||
## API Accessibility Summary
|
||||
|
||||
| Source | Type | Access Level | Cost | Feasibility |
|
||||
|---|---|---|---|---|
|
||||
| HEDD | Web Portal + Manual | Registered user | £1-5/check | Easy → Direct |
|
||||
| GMC Register | Public Web | Scrape/No API | Free | Easy → Scraper |
|
||||
| NMC Register | Public Web | Scrape/No API | Free | Easy → Scraper |
|
||||
| Companies House | REST API ✓ | Commercial | Free-£100/mo | Already done |
|
||||
| Directors API | REST API | Commercial | Included | Easy → Extend |
|
||||
| GOV.UK Professions | REST API | Open | Free | Easy → Query |
|
||||
| ICAEW Register | Public Web | Scrape/No API | Free | Medium → Scraper |
|
||||
| SRA Register | Public Web | Scrape/No API | Free | Medium → Scraper |
|
||||
| HMRC RTI | REST API | Restricted | Via partner | Hard → Partnership |
|
||||
| DBS | REST API | Via partner | £20-50/check | Hard → Partnership |
|
||||
|
||||
---
|
||||
|
||||
## Next Steps (This Week)
|
||||
|
||||
1. **Confirm HEDD feasibility** with legal/compliance (consent requirements, data handling)
|
||||
2. **Request GMC/NMC API access** officially (may grant vs. scraping)
|
||||
3. **Map ICAEW/SRA register structures** for scraper design
|
||||
4. **Contact HMRC/DBS vendors** (Verifile, DDC) for partnership exploration
|
||||
5. **UK recruiter interviews:** Validate prioritization with 10-15 target customers
|
||||
6. **Wireframe HEDD UI** in parallel with backend work
|
||||
|
||||
---
|
||||
|
||||
## References
|
||||
|
||||
- [HEDD (Higher Education Degree Datacheck)](https://hedd.ac.uk/)
|
||||
- [GMC Register](https://www.gmc-uk.org/registration-and-licensing/our-registers)
|
||||
- [NMC Register](https://www.nmc.org.uk/registration/search-the-register/)
|
||||
- [UK Regulated Professions Register](https://www.regulated-professions.service.gov.uk/)
|
||||
- [CV Fraud UK Statistics - Cifas](https://www.cifas.org.uk/)
|
||||
- [UK Employment Gaps Report 2025 - LiveCareer](https://www.livecareer.co.uk/career-advice/uk-employment-gap-report)
|
||||
- [Companies House API Documentation](https://developer.companieshouse.gov.uk/)
|
||||
@@ -1,416 +0,0 @@
|
||||
# TrueCV UK Market Strategy & Product Roadmap
|
||||
|
||||
**Document Date:** January 2026
|
||||
**Focus:** UK CV verification market positioning
|
||||
**Horizon:** 12-month growth plan
|
||||
|
||||
---
|
||||
|
||||
## Market Opportunity
|
||||
|
||||
### Problem Statement
|
||||
|
||||
**UK CV fraud costs employers £4.2B+ annually** and is accelerating:
|
||||
|
||||
- **1 in 5 candidates** claim false university degrees (Cifas, 2025)
|
||||
- **40% of CV embellishments** relate to qualifications
|
||||
- **24% of screened CVs** fail verification (Reed Screening, 2025)
|
||||
- **AI-generated fraud emerging:** Deepfakes, synthetic identities, proxy interviews now feasible
|
||||
- **Regulatory risk:** Companies failing due diligence face legal liability in regulated sectors
|
||||
|
||||
**Traditional Pre-Employment Screening is Broken:**
|
||||
- Manual background checks take 5-10 days
|
||||
- Education verification requires contacting universities individually (10 days+)
|
||||
- Professional registration checks vary by profession with no central access
|
||||
- No integrated view across employment, education, and professional credentials
|
||||
|
||||
---
|
||||
|
||||
## Market Size & Addressable Opportunity
|
||||
|
||||
### TAM (Total Addressable Market)
|
||||
|
||||
**UK Recruitment Market Segment:**
|
||||
- **190,000 recruitment companies** in UK (2024)
|
||||
- **~3,500+ recruiting via dedicated screening tools**
|
||||
- **Estimated £2.8B spent on background screening annually**
|
||||
|
||||
### SAM (Serviceable Addressable Market)
|
||||
|
||||
**TrueCV Target Segment:**
|
||||
- Mid-market recruitment agencies (50-500 staff): ~800 companies
|
||||
- Corporate HR departments (100+ employees): ~15,000 companies
|
||||
- Specialist vertical recruiters (healthcare, finance, legal): ~2,500 companies
|
||||
- **Total TAM:** ~18,300 potential customers
|
||||
|
||||
**Estimated SAM value at £180/customer/year:** ~£3.3M annually
|
||||
|
||||
### SOM (Serviceable Obtainable Market) - Year 1
|
||||
|
||||
- **Target:** 50-100 customers at £50-200/month (various tiers)
|
||||
- **Projected revenue:** £30-240K in Year 1
|
||||
- **Growth trajectory:** Doubling annually if market adoption strong
|
||||
|
||||
---
|
||||
|
||||
## Competitive Landscape
|
||||
|
||||
### Current Competitors
|
||||
|
||||
| Competitor | Coverage | Strength | Weakness |
|
||||
|---|---|---|---|
|
||||
| **Workable** | ATS + basic screening | Broad platform | No CV verification focus |
|
||||
| **Deel** | Global hiring + screening | Compliance authority | Not UK-focused; expensive |
|
||||
| **Checkr** | Background checks + DBS | Scale and integrations | No CV-specific verification |
|
||||
| **Verifile** | Pre-employment screening | Established relationships | Traditional manual process |
|
||||
| **Veriff** | Identity verification | Strong deepfake tech | Not employment-focused |
|
||||
|
||||
### TrueCV Differentiation
|
||||
|
||||
| Feature | TrueCV | Workable | Deel | Checkr | Verifile |
|
||||
|---|---|---|---|---|---|
|
||||
| **Degree Verification (HEDD)** | ✅ Q1 2026 | ❌ | ❌ | ❌ | ❌ |
|
||||
| **Healthcare Register Checks** | ✅ Q1 2026 | ❌ | ❌ | ❌ | ❌ |
|
||||
| **Timeline Fraud Detection** | ✅ Q1 2026 | ❌ | ❌ | ❌ | ❌ |
|
||||
| **Director Verification** | ✅ Q1 2026 | ❌ | ❌ | ❌ | ❌ |
|
||||
| **Companies House API** | ✅ | ❌ | ❌ | ❌ | ❌ |
|
||||
| **Professional Bodies** | ✅ Q2 2026 | ❌ | ❌ | Partial | Partial |
|
||||
| **DBS Integration** | ✅ Q3 2026 | ❌ | ✅ | ✅ | ✅ |
|
||||
|
||||
**Key Advantage:** *First-to-market with CV-specific, UK-integrated verification stack*
|
||||
|
||||
---
|
||||
|
||||
## Product Strategy
|
||||
|
||||
### Phase 1 (Q1 2026): MVP Launch - "The Verified CV"
|
||||
|
||||
**Positioning:** "Every UK degree verified. Every timeline verified. Every claim validated."
|
||||
|
||||
**Core Features (8 weeks):**
|
||||
1. HEDD degree verification (real-time + manual review tracking)
|
||||
2. Timeline fraud detection (overlaps, gaps, education-employment sequencing)
|
||||
3. Companies House director verification
|
||||
4. GMC/NMC healthcare register checks
|
||||
5. Enhanced CV parsing (education/employment entity linking)
|
||||
|
||||
**Target Customer:** Medium recruitment agencies (50-200 hiring/year)
|
||||
|
||||
**Pricing Model:**
|
||||
- **Freemium:** 3 free CV checks/month (companies house + timeline)
|
||||
- **Professional:** £49/month - Unlimited checks + HEDD verification
|
||||
- **Enterprise:** £199/month - Pro features + API access + custom integrations
|
||||
|
||||
**Launch Strategy:**
|
||||
- Partner with 3-5 recruitment agencies for beta testing (Week 2-4)
|
||||
- Launch public beta (Week 5)
|
||||
- GA release (Week 8)
|
||||
- Press/analyst outreach highlighting fraud prevention angle
|
||||
|
||||
**Success Metrics:**
|
||||
- 500+ signups in first 4 weeks
|
||||
- 10%+ weekly active check rate
|
||||
- 85%+ feature satisfaction (NPS >40)
|
||||
|
||||
---
|
||||
|
||||
### Phase 2 (Q2 2026): Professional Bodies Expansion
|
||||
|
||||
**Positioning:** "Verify degrees. Verify certifications. Verify everything."
|
||||
|
||||
**New Features:**
|
||||
1. ICAEW accountant registration checks
|
||||
2. SRA solicitor registration checks
|
||||
3. IET engineer registration checks
|
||||
4. RIBA architect registration checks
|
||||
5. GOV.UK regulated profession enrichment (API layer)
|
||||
|
||||
**Target Market Expansion:**
|
||||
- Financial services recruiting
|
||||
- Legal recruiting
|
||||
- Engineering recruiting
|
||||
- Niche vertical specialists
|
||||
|
||||
**Business Impact:**
|
||||
- +40% monthly active users
|
||||
- +3x engagement (more verifications per customer)
|
||||
- +2x ARPU (professional tier premium)
|
||||
|
||||
**Pricing:** Professional tier → £79/month; Enterprise → £249/month
|
||||
|
||||
---
|
||||
|
||||
### Phase 3 (Q3 2026): Compliance & Regulatory
|
||||
|
||||
**Positioning:** "Full employment verification. Complete compliance confidence."
|
||||
|
||||
**New Features:**
|
||||
1. HMRC payroll verification (partnership model)
|
||||
2. DBS check integration (partnership + commission)
|
||||
3. Right-to-Work verification API
|
||||
4. Audit trail & compliance reporting
|
||||
5. Batch processing API
|
||||
|
||||
**Target Markets:**
|
||||
- Large corporate HR departments
|
||||
- Pre-employment screening agencies
|
||||
- Temp/staffing agencies (compliance-heavy)
|
||||
|
||||
**Business Model Evolution:**
|
||||
- Per-check commission on DBS (£5-15 per check)
|
||||
- HMRC verification licensing (custom pricing)
|
||||
- API/platform access (£500-2,000/month)
|
||||
|
||||
---
|
||||
|
||||
## Go-To-Market Strategy
|
||||
|
||||
### Sales Channels
|
||||
|
||||
#### Channel 1: Direct Sales (Lead)
|
||||
- Target recruitment agency owners (LinkedIn outreach)
|
||||
- HR directors at 100+ employee companies
|
||||
- Sales pitch: "Reduce hiring risk. Verify every claim. In minutes."
|
||||
- Expected conversion: 5-8% from qualified leads
|
||||
- Sales cycle: 2-4 weeks
|
||||
|
||||
#### Channel 2: Partnerships
|
||||
- Integrate with ATS platforms (Workable, Bullhorn, Lever)
|
||||
- White-label for existing background check providers
|
||||
- API marketplace (Zapier, Make, etc.)
|
||||
- Expected impact: +30% user acquisition annually
|
||||
|
||||
#### Channel 3: Content & SEO
|
||||
- Blog: "UK CV Fraud Patterns" (targeting "CV verification UK" search)
|
||||
- Case studies: "How we caught 18 fake degrees in Q1"
|
||||
- Thought leadership: Webinars on fraud detection for HR
|
||||
- Expected impact: +20% organic users
|
||||
|
||||
#### Channel 4: Vertical Specialists
|
||||
- Healthcare recruiter outreach (GMC/NMC checks as entry)
|
||||
- Financial services (ICAEW verification)
|
||||
- Legal recruiting (SRA verification)
|
||||
- Expected impact: +25% high-value customers
|
||||
|
||||
### Marketing Messaging
|
||||
|
||||
**Tagline:** "Hire with Confidence. Verify with TrueCV."
|
||||
|
||||
**Core Messages:**
|
||||
1. **For Recruiters:** "Catch 90% of degree fraud in seconds. One-click HEDD verification."
|
||||
2. **For HR Teams:** "Complete CV validation pipeline. Reduce hiring risk by 70%."
|
||||
3. **For Compliance:** "Full audit trail. DBS integration. Regulatory confidence."
|
||||
|
||||
**Proof Points:**
|
||||
- "Trusted by 5+ UK recruitment agencies in first 30 days"
|
||||
- "1 in 5 candidates have false degrees — we find them"
|
||||
- "Average savings: £8,000 per bad hire prevented"
|
||||
|
||||
---
|
||||
|
||||
## Pricing Strategy
|
||||
|
||||
### Tier Analysis
|
||||
|
||||
| Tier | Users | Monthly | Annual | Features |
|
||||
|---|---|---|---|---|
|
||||
| **Free** | Solo recruiters | £0 | £0 | 3 CV checks, Companies House |
|
||||
| **Professional** | Small agencies | £49 | £490 | Unlimited checks, HEDD, Timeline |
|
||||
| **Enterprise** | Large orgs | £199 | £1,990 | API access, custom integrations, DBS |
|
||||
| **API/Platform** | Integration partners | £500-2K | £6-24K | Batch processing, white-label |
|
||||
|
||||
### Unit Economics (Target - Year 2)
|
||||
|
||||
- **Customer Acquisition Cost (CAC):** £150-300 (organic/partner-led)
|
||||
- **Average Revenue Per User (ARPU):** £60-120/month (mix of tiers)
|
||||
- **Payback Period:** 2-4 months
|
||||
- **Gross Margin:** 75-80% (SaaS model)
|
||||
- **LTV:CAC Ratio:** 4:1+ (healthy SaaS)
|
||||
|
||||
---
|
||||
|
||||
## Organizational Requirements
|
||||
|
||||
### Team Structure (12-month horizon)
|
||||
|
||||
**Now (Q1):**
|
||||
- 2 Backend Engineers (full-time on Phase 1)
|
||||
- 1 QA Engineer
|
||||
- 1 Product Manager
|
||||
- 1 Marketing/Growth Lead
|
||||
|
||||
**Q2 Addition:**
|
||||
- +1 Full-stack Engineer (vertical expansion)
|
||||
- +1 Sales/BD Lead (partnership development)
|
||||
|
||||
**Q3 Addition:**
|
||||
- +1 Customer Success Manager (onboarding)
|
||||
- +1 Part-time Data Analyst (metrics/LTV)
|
||||
|
||||
---
|
||||
|
||||
## Key Risks & Mitigations
|
||||
|
||||
| Risk | Probability | Impact | Mitigation |
|
||||
|---|---|---|---|
|
||||
| **HEDD API access delayed** | Medium | High | Use web portal integration; request API access in parallel |
|
||||
| **NHS/GMC scraping blocked** | Low | Medium | Request official API access proactively; provide value-add |
|
||||
| **Regulatory gatekeeping** | Medium | Medium | Build partnerships early; engage with regulators directly |
|
||||
| **DBS/HMRC integration delays** | Medium | Medium | Partner with established vendors (Verifile, DDC) handling compliance |
|
||||
| **Market adoption slower than expected** | Medium | Medium | Focus on high-value verticals first (healthcare, finance); expand TAM gradually |
|
||||
| **Competitor response** | Medium | Medium | Maintain first-mover advantage; deepen integrations; expand internationally (Ireland, Australia next) |
|
||||
|
||||
---
|
||||
|
||||
## Success Metrics (12-Month Goals)
|
||||
|
||||
### Business Metrics
|
||||
- **Revenue:** £250K+ annualized (Mix of Professional/Enterprise)
|
||||
- **Customers:** 50-75 paying customers
|
||||
- **Monthly Recurring Revenue (MRR):** £20K+
|
||||
- **CAC Payback:** <4 months
|
||||
- **NPS:** >50 (positive)
|
||||
|
||||
### Product Metrics
|
||||
- **HEDD Verification Accuracy:** >98% match rate
|
||||
- **Timeline Detection Rate:** 85%+ of actual gaps/overlaps caught
|
||||
- **Directorship Verification:** 95%+ accuracy (vs. Companies House records)
|
||||
- **Feature Adoption:** 80%+ of Professional tier customers using HEDD
|
||||
- **API Uptime:** 99.9%
|
||||
|
||||
### Market Metrics
|
||||
- **Brand Awareness:** 15%+ of recruitment agencies aware of TrueCV
|
||||
- **Market Share:** 0.5-1% of addressable recruitment screening market
|
||||
- **Vertical Penetration:** 3%+ of healthcare recruiters, 2%+ financial recruiters
|
||||
|
||||
---
|
||||
|
||||
## Financial Projections
|
||||
|
||||
### Conservative Scenario (50 customers by end-of-year)
|
||||
|
||||
**Revenue Breakdown:**
|
||||
- 30 × Professional tier at £49/mo: £1,470/mo
|
||||
- 15 × Enterprise tier at £199/mo: £2,985/mo
|
||||
- 5 × API/Platform at £1,000/mo: £5,000/mo
|
||||
- **Total MRR (Dec 2026):** £9,455
|
||||
- **Annualized:** £113,460
|
||||
|
||||
**Costs:**
|
||||
- Engineering (2 FTE): £150K/year
|
||||
- Infrastructure/APIs: £20K/year
|
||||
- Sales/Marketing: £30K/year
|
||||
- Operations: £20K/year
|
||||
- **Total Cost:** £220K/year
|
||||
|
||||
**Result:** Break-even at ~24 customers; profitable at 50+ customers
|
||||
|
||||
### Growth Scenario (100 customers by end-of-year)
|
||||
|
||||
- **MRR (Dec 2026):** £18,910
|
||||
- **Annualized Revenue:** £226,920
|
||||
- **Gross Margin:** 75% = £170K+ operational profit
|
||||
|
||||
---
|
||||
|
||||
## Next 30 Days Action Plan
|
||||
|
||||
### Week 1-2: Preparation
|
||||
- [ ] Contact HEDD for API access / partner discussions
|
||||
- [ ] Reach out to GMC/NMC about verification APIs
|
||||
- [ ] Identify 5 recruitment agency beta partners
|
||||
- [ ] Finalize HEDD compliance & consent workflows
|
||||
|
||||
### Week 2-4: Development
|
||||
- [ ] Complete HEDD integration (see Phase 1 technical doc)
|
||||
- [ ] GMC/NMC scraper development
|
||||
- [ ] Enhanced timeline analysis
|
||||
- [ ] Companies House director verification
|
||||
|
||||
### Week 3-4: Beta & Validation
|
||||
- [ ] Beta launch with 3-5 agencies
|
||||
- [ ] Collect feedback on UX/value
|
||||
- [ ] Measure fraud detection accuracy
|
||||
- [ ] Iterate on flag messaging/severity
|
||||
|
||||
### Week 4+: Go-to-Market
|
||||
- [ ] Public launch announcement
|
||||
- [ ] Initial outreach to 20-30 qualified prospects
|
||||
- [ ] Content marketing (first blog post live)
|
||||
- [ ] Track signup rate & activation
|
||||
|
||||
---
|
||||
|
||||
## Long-Term Vision (2-3 Years)
|
||||
|
||||
**Expansion Opportunities Beyond UK:**
|
||||
|
||||
### Ireland (Natural Extension)
|
||||
- Similar legal framework to UK
|
||||
- HEDD equivalent exists (HEA)
|
||||
- Revenue opportunity: +£50-100K annually
|
||||
|
||||
### Australia/New Zealand
|
||||
- English-speaking markets
|
||||
- Similar professional regulation frameworks
|
||||
- Revenue opportunity: +£150-300K annually
|
||||
|
||||
### EU Markets (Longer-term)
|
||||
- Different regulatory landscape (more complex)
|
||||
- ENIC-NARIC degree verification network
|
||||
- Professional body registration varies by country
|
||||
|
||||
### Vertical Integrations
|
||||
- HR software integrations (Workday, SAP SuccessFactors)
|
||||
- Talent acquisition platforms (multi-tool suites)
|
||||
- Compliance software (audit logging, reporting)
|
||||
|
||||
---
|
||||
|
||||
## Conclusion
|
||||
|
||||
TrueCV addresses a massive UK market problem (£4.2B+ annual cost from CV fraud) with a focused, integrated solution. By launching with HEDD degree verification + timeline fraud detection in Q1 2026, we capture first-mover advantage in a gap no competitor fills.
|
||||
|
||||
**The opportunity:** Become the UK's trusted CV verification layer for recruitment, reducing fraud while accelerating hiring processes.
|
||||
|
||||
**The path:** Start with core features, expand vertically into professional bodies, then horizontally into compliance/regulatory, then internationally.
|
||||
|
||||
**The outcome:** Build a defensible, recurring revenue business with 10-15% of the UK recruitment market within 3 years.
|
||||
|
||||
---
|
||||
|
||||
## Appendix: Customer Personas
|
||||
|
||||
### Persona 1: Agency Owner (Mid-Market)
|
||||
- **Name:** Sarah, 48, runs recruitment agency (80 staff)
|
||||
- **Problem:** Wasting 2-3 hours per hire verifying degrees manually
|
||||
- **Budget:** £3K-8K/year on screening tools
|
||||
- **Decision-making:** Speed & cost focus; wants manual verification eliminated
|
||||
- **Buying signal:** "Can you reduce our verification time from 5 days to 5 minutes?"
|
||||
|
||||
### Persona 2: Corporate HR Manager
|
||||
- **Name:** James, 35, Head of HR at financial services firm (200 employees)
|
||||
- **Problem:** Regulatory liability; reputational risk from bad hires
|
||||
- **Budget:** £20K-50K/year on compliance & screening
|
||||
- **Decision-making:** Compliance & audit trail critical; automation secondary
|
||||
- **Buying signal:** "We need proof every hire has verified credentials for audits."
|
||||
|
||||
### Persona 3: Specialist Vertical Recruiter
|
||||
- **Name:** Dr. Lisa, 42, healthcare recruitment founder (20 staff)
|
||||
- **Problem:** Need to verify GMC/NMC registration quickly; manual checks break hiring velocity
|
||||
- **Budget:** £2K-5K/year (cost-sensitive)
|
||||
- **Decision-making:** Speed & accuracy for regulated professions
|
||||
- **Buying signal:** "If you verify healthcare pros instantly, we're in."
|
||||
|
||||
---
|
||||
|
||||
## References & Data Sources
|
||||
|
||||
- [CIFAS CV Fraud Report](https://www.cifas.org.uk/newsroom/1-in-5-lie-about-uni-degree-cv-fraud)
|
||||
- [Reed Screening CV Verification Data](https://www.reed.com/)
|
||||
- [UK Employment Gap Report 2025](https://www.livecareer.co.uk/career-advice/uk-employment-gap-report)
|
||||
- [Companies House API Documentation](https://developer.companieshouse.gov.uk/)
|
||||
- [HEDD (Higher Education Degree Datacheck)](https://hedd.ac.uk/)
|
||||
- [GMC Register](https://www.gmc-uk.org/registration-and-licensing/our-registers)
|
||||
- [NMC Register](https://www.nmc.org.uk/registration/search-the-register/)
|
||||
|
||||
31
deploy-local.sh
Executable file
31
deploy-local.sh
Executable file
@@ -0,0 +1,31 @@
|
||||
#!/bin/bash
|
||||
# Deploy RealCV from local git repo to website
|
||||
set -e
|
||||
|
||||
cd /git/RealCV
|
||||
|
||||
echo "Building application..."
|
||||
dotnet publish src/RealCV.Web -c Release -o ./publish --nologo
|
||||
|
||||
echo "Stopping service..."
|
||||
sudo systemctl stop realcv
|
||||
|
||||
echo "Backing up config..."
|
||||
cp /var/www/realcv/appsettings.Production.json /tmp/appsettings.Production.json 2>/dev/null || true
|
||||
|
||||
echo "Deploying files..."
|
||||
sudo rm -rf /var/www/realcv/*
|
||||
sudo cp -r ./publish/* /var/www/realcv/
|
||||
|
||||
echo "Restoring config..."
|
||||
sudo cp /tmp/appsettings.Production.json /var/www/realcv/ 2>/dev/null || true
|
||||
|
||||
echo "Setting permissions..."
|
||||
sudo chown -R www-data:www-data /var/www/realcv
|
||||
|
||||
echo "Starting service..."
|
||||
sudo systemctl start realcv
|
||||
|
||||
echo "Done! Checking status..."
|
||||
sleep 2
|
||||
sudo systemctl is-active realcv && echo "Service is running."
|
||||
161
deploy/README.md
161
deploy/README.md
@@ -1,161 +0,0 @@
|
||||
# TrueCV Deployment Guide
|
||||
|
||||
## Quick Start
|
||||
|
||||
### 1. Server Setup (run once on fresh Ubuntu server)
|
||||
|
||||
```bash
|
||||
# Copy server-setup.sh to your server
|
||||
scp deploy/server-setup.sh user@your-server:/tmp/
|
||||
|
||||
# SSH into server and run setup
|
||||
ssh user@your-server
|
||||
sudo bash /tmp/server-setup.sh
|
||||
```
|
||||
|
||||
**Before running**, edit the script and update:
|
||||
- `DOMAIN` - Your domain name
|
||||
- `DB_PASSWORD` - Strong password for SQL Server
|
||||
- `ADMIN_EMAIL` - Email for SSL certificate notifications
|
||||
|
||||
### 2. Deploy Application (run from dev machine)
|
||||
|
||||
```bash
|
||||
# Edit deploy.sh and update configuration
|
||||
nano deploy/deploy.sh
|
||||
|
||||
# Make executable and run
|
||||
chmod +x deploy/deploy.sh
|
||||
./deploy/deploy.sh
|
||||
```
|
||||
|
||||
**Update these values in deploy.sh:**
|
||||
- `SERVER_USER` - SSH username
|
||||
- `SERVER_HOST` - Server hostname or IP
|
||||
- `DOMAIN` - Your domain name
|
||||
|
||||
### 3. Enable SSL
|
||||
|
||||
After DNS is configured and app is deployed:
|
||||
|
||||
```bash
|
||||
ssh user@your-server
|
||||
sudo certbot --nginx -d truecv.yourdomain.com
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Environment Variables
|
||||
|
||||
The systemd service sets these environment variables:
|
||||
- `ASPNETCORE_ENVIRONMENT=Production`
|
||||
- `ASPNETCORE_URLS=http://localhost:5000`
|
||||
- `ConnectionStrings__DefaultConnection=...`
|
||||
|
||||
To add more (like API keys), edit:
|
||||
```bash
|
||||
sudo systemctl edit truecv
|
||||
```
|
||||
|
||||
Add:
|
||||
```ini
|
||||
[Service]
|
||||
Environment=OpenAI__ApiKey=your-key-here
|
||||
```
|
||||
|
||||
### appsettings.Production.json
|
||||
|
||||
For sensitive settings, create `/var/www/truecv/appsettings.Production.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"ConnectionStrings": {
|
||||
"DefaultConnection": "Server=127.0.0.1;Database=TrueCV;User Id=SA;Password=YourPassword;TrustServerCertificate=True"
|
||||
},
|
||||
"OpenAI": {
|
||||
"ApiKey": "your-openai-key"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Maintenance
|
||||
|
||||
### View Logs
|
||||
```bash
|
||||
# Application logs
|
||||
sudo journalctl -u truecv -f
|
||||
|
||||
# Nginx logs
|
||||
sudo tail -f /var/log/nginx/access.log
|
||||
sudo tail -f /var/log/nginx/error.log
|
||||
|
||||
# SQL Server logs
|
||||
docker logs truecv-sql -f
|
||||
```
|
||||
|
||||
### Restart Services
|
||||
```bash
|
||||
sudo systemctl restart truecv
|
||||
sudo systemctl restart nginx
|
||||
docker restart truecv-sql
|
||||
```
|
||||
|
||||
### Database Backup
|
||||
```bash
|
||||
# Backup
|
||||
docker exec truecv-sql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U SA -P 'YourPassword' -C \
|
||||
-Q "BACKUP DATABASE TrueCV TO DISK='/var/opt/mssql/backup/truecv.bak'"
|
||||
|
||||
# Copy backup from container
|
||||
docker cp truecv-sql:/var/opt/mssql/backup/truecv.bak ./truecv-backup.bak
|
||||
```
|
||||
|
||||
### Rollback Deployment
|
||||
```bash
|
||||
# On server - restore previous version
|
||||
sudo systemctl stop truecv
|
||||
sudo rm -rf /var/www/truecv
|
||||
sudo mv /var/www/truecv.backup.YYYYMMDD_HHMMSS /var/www/truecv
|
||||
sudo systemctl start truecv
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
||||
### App won't start
|
||||
```bash
|
||||
# Check status
|
||||
sudo systemctl status truecv
|
||||
|
||||
# Check logs
|
||||
sudo journalctl -u truecv -n 100
|
||||
|
||||
# Test manually
|
||||
cd /var/www/truecv
|
||||
sudo -u www-data dotnet TrueCV.Web.dll
|
||||
```
|
||||
|
||||
### Database connection issues
|
||||
```bash
|
||||
# Check SQL Server is running
|
||||
docker ps | grep truecv-sql
|
||||
|
||||
# Test connection
|
||||
docker exec -it truecv-sql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U SA -P 'YourPassword' -C \
|
||||
-Q "SELECT name FROM sys.databases"
|
||||
```
|
||||
|
||||
### Blazor SignalR issues
|
||||
Ensure Nginx is configured for WebSocket support (included in setup script).
|
||||
|
||||
Check browser console for connection errors.
|
||||
|
||||
## Security Checklist
|
||||
|
||||
- [ ] Change default SQL Server password
|
||||
- [ ] Enable SSL with Let's Encrypt
|
||||
- [ ] Configure firewall (UFW)
|
||||
- [ ] Set up automated backups
|
||||
- [ ] Enable fail2ban for SSH protection
|
||||
- [ ] Keep system updated regularly
|
||||
@@ -1,5 +1,5 @@
|
||||
#!/bin/bash
|
||||
# TrueCV Deployment Script
|
||||
# RealCV Deployment Script
|
||||
# Run this from your development machine to deploy to a Linux server
|
||||
|
||||
set -e
|
||||
@@ -7,8 +7,8 @@ set -e
|
||||
# Configuration - UPDATE THESE VALUES
|
||||
SERVER_USER="deploy"
|
||||
SERVER_HOST="your-server.com"
|
||||
SERVER_PATH="/var/www/truecv"
|
||||
DOMAIN="truecv.yourdomain.com"
|
||||
SERVER_PATH="/var/www/realcv"
|
||||
DOMAIN="realcv.yourdomain.com"
|
||||
|
||||
# Colors for output
|
||||
RED='\033[0;31m'
|
||||
@@ -16,7 +16,7 @@ GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m' # No Color
|
||||
|
||||
echo -e "${GREEN}=== TrueCV Deployment Script ===${NC}"
|
||||
echo -e "${GREEN}=== RealCV Deployment Script ===${NC}"
|
||||
|
||||
# Check if configuration is set
|
||||
if [[ "$SERVER_HOST" == "your-server.com" ]]; then
|
||||
@@ -27,15 +27,15 @@ fi
|
||||
# Step 1: Build and publish
|
||||
echo -e "${YELLOW}Step 1: Publishing application...${NC}"
|
||||
cd "$(dirname "$0")/.."
|
||||
dotnet publish src/TrueCV.Web -c Release -o ./publish --nologo
|
||||
dotnet publish src/RealCV.Web -c Release -o ./publish --nologo
|
||||
|
||||
# Step 2: Create deployment package
|
||||
echo -e "${YELLOW}Step 2: Creating deployment package...${NC}"
|
||||
tar -czf deploy/truecv-release.tar.gz -C publish .
|
||||
tar -czf deploy/realcv-release.tar.gz -C publish .
|
||||
|
||||
# Step 3: Transfer to server
|
||||
echo -e "${YELLOW}Step 3: Transferring to server...${NC}"
|
||||
scp deploy/truecv-release.tar.gz ${SERVER_USER}@${SERVER_HOST}:/tmp/
|
||||
scp deploy/realcv-release.tar.gz ${SERVER_USER}@${SERVER_HOST}:/tmp/
|
||||
|
||||
# Step 4: Deploy on server
|
||||
echo -e "${YELLOW}Step 4: Deploying on server...${NC}"
|
||||
@@ -43,23 +43,23 @@ ssh ${SERVER_USER}@${SERVER_HOST} << 'ENDSSH'
|
||||
set -e
|
||||
|
||||
# Stop the service if running
|
||||
sudo systemctl stop truecv 2>/dev/null || true
|
||||
sudo systemctl stop realcv 2>/dev/null || true
|
||||
|
||||
# Backup current deployment
|
||||
if [ -d "/var/www/truecv" ]; then
|
||||
sudo mv /var/www/truecv /var/www/truecv.backup.$(date +%Y%m%d_%H%M%S)
|
||||
if [ -d "/var/www/realcv" ]; then
|
||||
sudo mv /var/www/realcv /var/www/realcv.backup.$(date +%Y%m%d_%H%M%S)
|
||||
fi
|
||||
|
||||
# Create directory and extract
|
||||
sudo mkdir -p /var/www/truecv
|
||||
sudo tar -xzf /tmp/truecv-release.tar.gz -C /var/www/truecv
|
||||
sudo chown -R www-data:www-data /var/www/truecv
|
||||
sudo mkdir -p /var/www/realcv
|
||||
sudo tar -xzf /tmp/realcv-release.tar.gz -C /var/www/realcv
|
||||
sudo chown -R www-data:www-data /var/www/realcv
|
||||
|
||||
# Start the service
|
||||
sudo systemctl start truecv
|
||||
sudo systemctl start realcv
|
||||
|
||||
# Clean up
|
||||
rm /tmp/truecv-release.tar.gz
|
||||
rm /tmp/realcv-release.tar.gz
|
||||
|
||||
echo "Deployment complete on server"
|
||||
ENDSSH
|
||||
@@ -67,14 +67,14 @@ ENDSSH
|
||||
# Step 5: Verify deployment
|
||||
echo -e "${YELLOW}Step 5: Verifying deployment...${NC}"
|
||||
sleep 3
|
||||
if ssh ${SERVER_USER}@${SERVER_HOST} "sudo systemctl is-active truecv" | grep -q "active"; then
|
||||
if ssh ${SERVER_USER}@${SERVER_HOST} "sudo systemctl is-active realcv" | grep -q "active"; then
|
||||
echo -e "${GREEN}=== Deployment successful! ===${NC}"
|
||||
echo -e "Site should be available at: https://${DOMAIN}"
|
||||
else
|
||||
echo -e "${RED}Warning: Service may not be running. Check with: sudo systemctl status truecv${NC}"
|
||||
echo -e "${RED}Warning: Service may not be running. Check with: sudo systemctl status realcv${NC}"
|
||||
fi
|
||||
|
||||
# Cleanup local files
|
||||
rm -f deploy/truecv-release.tar.gz
|
||||
rm -f deploy/realcv-release.tar.gz
|
||||
|
||||
echo -e "${GREEN}Done!${NC}"
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
#!/bin/bash
|
||||
# TrueCV Server Setup Script
|
||||
# RealCV Server Setup Script
|
||||
# Run this ONCE on a fresh Linux server (Ubuntu 22.04/24.04)
|
||||
|
||||
set -e
|
||||
|
||||
# Configuration - UPDATE THESE VALUES
|
||||
DOMAIN="truecv.yourdomain.com"
|
||||
DOMAIN="realcv.yourdomain.com"
|
||||
DB_PASSWORD="YourStrong!Password123"
|
||||
ADMIN_EMAIL="admin@yourdomain.com"
|
||||
|
||||
@@ -15,7 +15,7 @@ GREEN='\033[0;32m'
|
||||
YELLOW='\033[1;33m'
|
||||
NC='\033[0m'
|
||||
|
||||
echo -e "${GREEN}=== TrueCV Server Setup ===${NC}"
|
||||
echo -e "${GREEN}=== RealCV Server Setup ===${NC}"
|
||||
|
||||
# Check if running as root
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
@@ -52,54 +52,54 @@ echo -e "${YELLOW}Step 5: Setting up SQL Server...${NC}"
|
||||
docker run -e 'ACCEPT_EULA=Y' \
|
||||
-e "SA_PASSWORD=${DB_PASSWORD}" \
|
||||
-p 127.0.0.1:1433:1433 \
|
||||
--name truecv-sql \
|
||||
--name realcv-sql \
|
||||
--restart unless-stopped \
|
||||
-v truecv-sqldata:/var/opt/mssql \
|
||||
-v realcv-sqldata:/var/opt/mssql \
|
||||
-d mcr.microsoft.com/mssql/server:2022-latest
|
||||
|
||||
echo "Waiting for SQL Server to start..."
|
||||
sleep 30
|
||||
|
||||
# Create the database
|
||||
docker exec truecv-sql /opt/mssql-tools18/bin/sqlcmd \
|
||||
docker exec realcv-sql /opt/mssql-tools18/bin/sqlcmd \
|
||||
-S localhost -U SA -P "${DB_PASSWORD}" -C \
|
||||
-Q "CREATE DATABASE TrueCV"
|
||||
-Q "CREATE DATABASE RealCV"
|
||||
|
||||
# Step 6: Create application directory
|
||||
echo -e "${YELLOW}Step 6: Creating application directory...${NC}"
|
||||
mkdir -p /var/www/truecv
|
||||
chown -R www-data:www-data /var/www/truecv
|
||||
mkdir -p /var/www/realcv
|
||||
chown -R www-data:www-data /var/www/realcv
|
||||
|
||||
# Step 7: Create systemd service
|
||||
echo -e "${YELLOW}Step 7: Creating systemd service...${NC}"
|
||||
cat > /etc/systemd/system/truecv.service << EOF
|
||||
cat > /etc/systemd/system/realcv.service << EOF
|
||||
[Unit]
|
||||
Description=TrueCV Web Application
|
||||
Description=RealCV Web Application
|
||||
After=network.target docker.service
|
||||
Requires=docker.service
|
||||
|
||||
[Service]
|
||||
WorkingDirectory=/var/www/truecv
|
||||
ExecStart=/usr/bin/dotnet /var/www/truecv/TrueCV.Web.dll
|
||||
WorkingDirectory=/var/www/realcv
|
||||
ExecStart=/usr/bin/dotnet /var/www/realcv/RealCV.Web.dll
|
||||
Restart=always
|
||||
RestartSec=10
|
||||
KillSignal=SIGINT
|
||||
SyslogIdentifier=truecv
|
||||
SyslogIdentifier=realcv
|
||||
User=www-data
|
||||
Environment=ASPNETCORE_ENVIRONMENT=Production
|
||||
Environment=ASPNETCORE_URLS=http://localhost:5000
|
||||
Environment=ConnectionStrings__DefaultConnection=Server=127.0.0.1;Database=TrueCV;User Id=SA;Password=${DB_PASSWORD};TrustServerCertificate=True
|
||||
Environment=ConnectionStrings__DefaultConnection=Server=127.0.0.1;Database=RealCV;User Id=SA;Password=${DB_PASSWORD};TrustServerCertificate=True
|
||||
|
||||
[Install]
|
||||
WantedBy=multi-user.target
|
||||
EOF
|
||||
|
||||
systemctl daemon-reload
|
||||
systemctl enable truecv
|
||||
systemctl enable realcv
|
||||
|
||||
# Step 8: Configure Nginx
|
||||
echo -e "${YELLOW}Step 8: Configuring Nginx...${NC}"
|
||||
cat > /etc/nginx/sites-available/truecv << EOF
|
||||
cat > /etc/nginx/sites-available/realcv << EOF
|
||||
server {
|
||||
listen 80;
|
||||
server_name ${DOMAIN};
|
||||
@@ -122,7 +122,7 @@ server {
|
||||
}
|
||||
EOF
|
||||
|
||||
ln -sf /etc/nginx/sites-available/truecv /etc/nginx/sites-enabled/
|
||||
ln -sf /etc/nginx/sites-available/realcv /etc/nginx/sites-enabled/
|
||||
rm -f /etc/nginx/sites-enabled/default
|
||||
nginx -t
|
||||
systemctl reload nginx
|
||||
@@ -151,9 +151,9 @@ echo "2. Deploy the application using deploy.sh from your dev machine"
|
||||
echo "3. Run SSL setup: certbot --nginx -d ${DOMAIN}"
|
||||
echo ""
|
||||
echo "Useful commands:"
|
||||
echo " sudo systemctl status truecv - Check app status"
|
||||
echo " sudo journalctl -u truecv -f - View app logs"
|
||||
echo " docker logs truecv-sql - View SQL Server logs"
|
||||
echo " sudo systemctl status realcv - Check app status"
|
||||
echo " sudo journalctl -u realcv -f - View app logs"
|
||||
echo " docker logs realcv-sql - View SQL Server logs"
|
||||
echo ""
|
||||
echo -e "${YELLOW}Database connection string:${NC}"
|
||||
echo " Server=127.0.0.1;Database=TrueCV;User Id=SA;Password=${DB_PASSWORD};TrustServerCertificate=True"
|
||||
echo " Server=127.0.0.1;Database=RealCV;User Id=SA;Password=${DB_PASSWORD};TrustServerCertificate=True"
|
||||
|
||||
@@ -1,18 +1,18 @@
|
||||
version: '3.8'
|
||||
|
||||
services:
|
||||
# TrueCV Web Application
|
||||
truecv-web:
|
||||
# RealCV Web Application
|
||||
realcv-web:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: truecv-web
|
||||
container_name: realcv-web
|
||||
ports:
|
||||
- "5000:8080"
|
||||
environment:
|
||||
- ASPNETCORE_ENVIRONMENT=Development
|
||||
- ConnectionStrings__DefaultConnection=Server=sqlserver;Database=TrueCV;User Id=sa;Password=TrueCV_P@ssw0rd!;TrustServerCertificate=True;
|
||||
- ConnectionStrings__HangfireConnection=Server=sqlserver;Database=TrueCV_Hangfire;User Id=sa;Password=TrueCV_P@ssw0rd!;TrustServerCertificate=True;
|
||||
- ConnectionStrings__DefaultConnection=Server=sqlserver;Database=RealCV;User Id=sa;Password=RealCV_P@ssw0rd!;TrustServerCertificate=True;
|
||||
- ConnectionStrings__HangfireConnection=Server=sqlserver;Database=RealCV_Hangfire;User Id=sa;Password=RealCV_P@ssw0rd!;TrustServerCertificate=True;
|
||||
- AzureBlob__ConnectionString=DefaultEndpointsProtocol=http;AccountName=devstoreaccount1;AccountKey=Eby8vdM02xNOcqFlqUwJPLlmEtlCDXJ1OUzFT50uSRZ6IFsuFq2UVErCz4I6tq/K1SZFPTOtr/KBHBeksoGMGw==;BlobEndpoint=http://azurite:10000/devstoreaccount1;
|
||||
- AzureBlob__ContainerName=cv-uploads
|
||||
- CompaniesHouse__BaseUrl=https://api.company-information.service.gov.uk
|
||||
@@ -24,25 +24,25 @@ services:
|
||||
azurite:
|
||||
condition: service_started
|
||||
networks:
|
||||
- truecv-network
|
||||
- realcv-network
|
||||
restart: unless-stopped
|
||||
|
||||
# SQL Server Database
|
||||
sqlserver:
|
||||
image: mcr.microsoft.com/mssql/server:2022-latest
|
||||
container_name: truecv-sqlserver
|
||||
container_name: realcv-sqlserver
|
||||
ports:
|
||||
- "1433:1433"
|
||||
environment:
|
||||
- ACCEPT_EULA=Y
|
||||
- MSSQL_SA_PASSWORD=TrueCV_P@ssw0rd!
|
||||
- MSSQL_SA_PASSWORD=RealCV_P@ssw0rd!
|
||||
- MSSQL_PID=Developer
|
||||
volumes:
|
||||
- sqlserver-data:/var/opt/mssql
|
||||
networks:
|
||||
- truecv-network
|
||||
- realcv-network
|
||||
healthcheck:
|
||||
test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "TrueCV_P@ssw0rd!" -C -Q "SELECT 1" || exit 1
|
||||
test: /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P "RealCV_P@ssw0rd!" -C -Q "SELECT 1" || exit 1
|
||||
interval: 10s
|
||||
timeout: 5s
|
||||
retries: 10
|
||||
@@ -52,7 +52,7 @@ services:
|
||||
# Azure Storage Emulator (Azurite)
|
||||
azurite:
|
||||
image: mcr.microsoft.com/azure-storage/azurite:latest
|
||||
container_name: truecv-azurite
|
||||
container_name: realcv-azurite
|
||||
ports:
|
||||
- "10000:10000" # Blob service
|
||||
- "10001:10001" # Queue service
|
||||
@@ -61,7 +61,7 @@ services:
|
||||
- azurite-data:/data
|
||||
command: "azurite --blobHost 0.0.0.0 --queueHost 0.0.0.0 --tableHost 0.0.0.0 --location /data --debug /data/debug.log"
|
||||
networks:
|
||||
- truecv-network
|
||||
- realcv-network
|
||||
restart: unless-stopped
|
||||
|
||||
# Database initialization (runs migrations)
|
||||
@@ -69,18 +69,18 @@ services:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile.migrations
|
||||
container_name: truecv-db-init
|
||||
container_name: realcv-db-init
|
||||
environment:
|
||||
- ConnectionStrings__DefaultConnection=Server=sqlserver;Database=TrueCV;User Id=sa;Password=TrueCV_P@ssw0rd!;TrustServerCertificate=True;
|
||||
- ConnectionStrings__DefaultConnection=Server=sqlserver;Database=RealCV;User Id=sa;Password=RealCV_P@ssw0rd!;TrustServerCertificate=True;
|
||||
depends_on:
|
||||
sqlserver:
|
||||
condition: service_healthy
|
||||
networks:
|
||||
- truecv-network
|
||||
- realcv-network
|
||||
restart: "no"
|
||||
|
||||
networks:
|
||||
truecv-network:
|
||||
realcv-network:
|
||||
driver: bridge
|
||||
|
||||
volumes:
|
||||
|
||||
BIN
realcv.png
Executable file
BIN
realcv.png
Executable file
Binary file not shown.
|
After Width: | Height: | Size: 6.3 MiB |
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.DTOs;
|
||||
namespace RealCV.Application.DTOs;
|
||||
|
||||
public sealed record CVCheckDto
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.DTOs;
|
||||
namespace RealCV.Application.DTOs;
|
||||
|
||||
public sealed record CompanySearchResult
|
||||
{
|
||||
16
src/RealCV.Application/DTOs/SubscriptionInfoDto.cs
Normal file
16
src/RealCV.Application/DTOs/SubscriptionInfoDto.cs
Normal file
@@ -0,0 +1,16 @@
|
||||
using RealCV.Domain.Enums;
|
||||
|
||||
namespace RealCV.Application.DTOs;
|
||||
|
||||
public class SubscriptionInfoDto
|
||||
{
|
||||
public UserPlan Plan { get; set; }
|
||||
public int ChecksUsedThisMonth { get; set; }
|
||||
public int MonthlyLimit { get; set; }
|
||||
public int ChecksRemaining { get; set; }
|
||||
public bool IsUnlimited { get; set; }
|
||||
public string? SubscriptionStatus { get; set; }
|
||||
public DateTime? CurrentPeriodEnd { get; set; }
|
||||
public bool HasActiveSubscription { get; set; }
|
||||
public string DisplayPrice { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Data;
|
||||
namespace RealCV.Application.Data;
|
||||
|
||||
/// <summary>
|
||||
/// Known diploma mills and fake educational institutions.
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Data;
|
||||
namespace RealCV.Application.Data;
|
||||
|
||||
/// <summary>
|
||||
/// List of recognised UK higher education institutions.
|
||||
@@ -156,6 +156,24 @@ public static class UKInstitutions
|
||||
"University for the Creative Arts",
|
||||
"Ravensbourne University London",
|
||||
|
||||
// Professional Bodies (accredited qualification-awarding)
|
||||
"CIPD",
|
||||
"Chartered Institute of Personnel and Development",
|
||||
"CIMA",
|
||||
"Chartered Institute of Management Accountants",
|
||||
"ACCA",
|
||||
"Association of Chartered Certified Accountants",
|
||||
"ICAEW",
|
||||
"Institute of Chartered Accountants in England and Wales",
|
||||
"ICAS",
|
||||
"Institute of Chartered Accountants of Scotland",
|
||||
"CII",
|
||||
"Chartered Insurance Institute",
|
||||
"CIPS",
|
||||
"Chartered Institute of Procurement and Supply",
|
||||
"CMI",
|
||||
"Chartered Management Institute",
|
||||
|
||||
// Business Schools (accredited)
|
||||
"Henley Business School",
|
||||
"Warwick Business School",
|
||||
@@ -168,6 +186,19 @@ public static class UKInstitutions
|
||||
"Cranfield School of Management",
|
||||
"Ashridge Business School",
|
||||
"Alliance Manchester Business School",
|
||||
|
||||
// Notable Further Education Colleges
|
||||
"Loughborough College",
|
||||
"City of Bristol College",
|
||||
"Newcastle College",
|
||||
"Leeds City College",
|
||||
"City College Norwich",
|
||||
"Weston College",
|
||||
"Chichester College",
|
||||
"Hartpury College",
|
||||
"Myerscough College",
|
||||
"Plumpton College",
|
||||
"Writtle University College",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -218,6 +249,77 @@ public static class UKInstitutions
|
||||
["Queen Mary"] = "Queen Mary University of London",
|
||||
["Royal Holloway University"] = "Royal Holloway, University of London",
|
||||
["RHUL"] = "Royal Holloway, University of London",
|
||||
["Hull University"] = "University of Hull",
|
||||
["Hull"] = "University of Hull",
|
||||
|
||||
// Additional "X University" variations for "University of X" institutions
|
||||
["Birmingham University"] = "University of Birmingham",
|
||||
["Bristol University"] = "University of Bristol",
|
||||
["Edinburgh University"] = "University of Edinburgh",
|
||||
["Exeter University"] = "University of Exeter",
|
||||
["Glasgow University"] = "University of Glasgow",
|
||||
["Leeds University"] = "University of Leeds",
|
||||
["Leicester University"] = "University of Leicester",
|
||||
["Liverpool University"] = "University of Liverpool",
|
||||
["Manchester University"] = "University of Manchester",
|
||||
["Nottingham University"] = "University of Nottingham",
|
||||
["Sheffield University"] = "University of Sheffield",
|
||||
["Southampton University"] = "University of Southampton",
|
||||
["Warwick University"] = "University of Warwick",
|
||||
["York University"] = "University of York",
|
||||
["Bath University"] = "University of Bath",
|
||||
["Bradford University"] = "University of Bradford",
|
||||
["Brighton University"] = "University of Brighton",
|
||||
["Derby University"] = "University of Derby",
|
||||
["Dundee University"] = "University of Dundee",
|
||||
["Essex University"] = "University of Essex",
|
||||
["Greenwich University"] = "University of Greenwich",
|
||||
["Hertfordshire University"] = "University of Hertfordshire",
|
||||
["Huddersfield University"] = "University of Huddersfield",
|
||||
["Kent University"] = "University of Kent",
|
||||
["Lincoln University"] = "University of Lincoln",
|
||||
["Plymouth University"] = "University of Plymouth",
|
||||
["Portsmouth University"] = "University of Portsmouth",
|
||||
["Reading University"] = "University of Reading",
|
||||
["Salford University"] = "University of Salford",
|
||||
["Surrey University"] = "University of Surrey",
|
||||
["Sussex University"] = "University of Sussex",
|
||||
["Westminster University"] = "University of Westminster",
|
||||
["Winchester University"] = "University of Winchester",
|
||||
["Wolverhampton University"] = "University of Wolverhampton",
|
||||
["Worcester University"] = "University of Worcester",
|
||||
["Aberdeen University"] = "University of Aberdeen",
|
||||
["Stirling University"] = "University of Stirling",
|
||||
["Strathclyde University"] = "University of Strathclyde",
|
||||
["Aberystwyth University"] = "Aberystwyth University",
|
||||
["Bangor University"] = "Bangor University",
|
||||
["Swansea University"] = "Swansea University",
|
||||
|
||||
// London university variations
|
||||
["UCL"] = "University College London",
|
||||
["University College, London"] = "University College London",
|
||||
["East London University"] = "University of East London",
|
||||
["London Metropolitan"] = "London Metropolitan University",
|
||||
["London Met"] = "London Metropolitan University",
|
||||
["South Bank University"] = "London South Bank University",
|
||||
["LSBU"] = "London South Bank University",
|
||||
|
||||
// Other common variations
|
||||
["Open University"] = "The Open University",
|
||||
["OU"] = "The Open University",
|
||||
["Northumbria"] = "Northumbria University",
|
||||
["De Montfort"] = "De Montfort University",
|
||||
["DMU"] = "De Montfort University",
|
||||
["Sheffield Hallam"] = "Sheffield Hallam University",
|
||||
["Nottingham Trent"] = "Nottingham Trent University",
|
||||
["NTU"] = "Nottingham Trent University",
|
||||
["Oxford Brookes"] = "Oxford Brookes University",
|
||||
["MMU"] = "Manchester Metropolitan University",
|
||||
["Manchester Met"] = "Manchester Metropolitan University",
|
||||
["Liverpool John Moores"] = "Liverpool John Moores University",
|
||||
["LJMU"] = "Liverpool John Moores University",
|
||||
["UWE"] = "University of the West of England",
|
||||
["West of England"] = "University of the West of England",
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
@@ -270,7 +372,40 @@ public static class UKInstitutions
|
||||
if (NameVariations.TryGetValue(normalised, out var officialName))
|
||||
return officialName;
|
||||
|
||||
// Fuzzy match
|
||||
// Try automatic "X University" ↔ "University of X" transformation
|
||||
var transformed = TryTransformUniversityName(normalised);
|
||||
if (transformed != null && RecognisedInstitutions.Contains(transformed))
|
||||
return transformed;
|
||||
|
||||
// Handle compound names (e.g., "Loughborough College/Motorsport UK Academy")
|
||||
// Split by common separators and check each part
|
||||
var separators = new[] { '/', '&', '-', '–', '—', ',' };
|
||||
if (normalised.IndexOfAny(separators) >= 0)
|
||||
{
|
||||
var parts = normalised.Split(separators, StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);
|
||||
foreach (var part in parts)
|
||||
{
|
||||
// Try direct match on part
|
||||
if (RecognisedInstitutions.Contains(part))
|
||||
return part;
|
||||
|
||||
// Try variation match on part
|
||||
if (NameVariations.TryGetValue(part, out var partOfficialName))
|
||||
return partOfficialName;
|
||||
|
||||
// Try fuzzy match on part
|
||||
foreach (var institution in RecognisedInstitutions)
|
||||
{
|
||||
if (institution.Contains(part, StringComparison.OrdinalIgnoreCase) ||
|
||||
part.Contains(institution, StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
return institution;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Fuzzy match on full name
|
||||
foreach (var institution in RecognisedInstitutions)
|
||||
{
|
||||
if (institution.Contains(normalised, StringComparison.OrdinalIgnoreCase) ||
|
||||
@@ -282,4 +417,27 @@ public static class UKInstitutions
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Attempts to transform university name between common formats:
|
||||
/// "X University" ↔ "University of X"
|
||||
/// </summary>
|
||||
private static string? TryTransformUniversityName(string name)
|
||||
{
|
||||
// Try "X University" → "University of X"
|
||||
if (name.EndsWith(" University", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var place = name[..^11].Trim(); // Remove " University"
|
||||
return $"University of {place}";
|
||||
}
|
||||
|
||||
// Try "University of X" → "X University"
|
||||
if (name.StartsWith("University of ", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
var place = name[14..].Trim(); // Remove "University of "
|
||||
return $"{place} University";
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Helpers;
|
||||
namespace RealCV.Application.Helpers;
|
||||
|
||||
public static class DateHelpers
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.Text.Json;
|
||||
|
||||
namespace TrueCV.Application.Helpers;
|
||||
namespace RealCV.Application.Helpers;
|
||||
|
||||
public static class JsonDefaults
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Helpers;
|
||||
namespace RealCV.Application.Helpers;
|
||||
|
||||
public static class ScoreThresholds
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Interfaces;
|
||||
namespace RealCV.Application.Interfaces;
|
||||
|
||||
public interface IAuditService
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using TrueCV.Application.DTOs;
|
||||
using TrueCV.Application.Models;
|
||||
using RealCV.Application.DTOs;
|
||||
using RealCV.Application.Models;
|
||||
|
||||
namespace TrueCV.Application.Interfaces;
|
||||
namespace RealCV.Application.Interfaces;
|
||||
|
||||
public interface ICVCheckService
|
||||
{
|
||||
@@ -11,4 +11,9 @@ public interface ICVCheckService
|
||||
Task<List<CVCheckDto>> GetUserChecksAsync(Guid userId);
|
||||
Task<VeracityReport?> GetReportAsync(Guid checkId, Guid userId);
|
||||
Task<bool> DeleteCheckAsync(Guid checkId, Guid userId);
|
||||
|
||||
/// <summary>
|
||||
/// GDPR: Delete all CV checks and associated data for a user (right to erasure).
|
||||
/// </summary>
|
||||
Task<int> DeleteAllUserDataAsync(Guid userId);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using TrueCV.Application.Models;
|
||||
using RealCV.Application.Models;
|
||||
|
||||
namespace TrueCV.Application.Interfaces;
|
||||
namespace RealCV.Application.Interfaces;
|
||||
|
||||
public interface ICVParserService
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using TrueCV.Application.Models;
|
||||
using RealCV.Application.Models;
|
||||
|
||||
namespace TrueCV.Application.Interfaces;
|
||||
namespace RealCV.Application.Interfaces;
|
||||
|
||||
public interface ICompanyNameMatcherService
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using TrueCV.Application.DTOs;
|
||||
using TrueCV.Application.Models;
|
||||
using RealCV.Application.DTOs;
|
||||
using RealCV.Application.Models;
|
||||
|
||||
namespace TrueCV.Application.Interfaces;
|
||||
namespace RealCV.Application.Interfaces;
|
||||
|
||||
public interface ICompanyVerifierService
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using TrueCV.Application.Models;
|
||||
using RealCV.Application.Models;
|
||||
|
||||
namespace TrueCV.Application.Interfaces;
|
||||
namespace RealCV.Application.Interfaces;
|
||||
|
||||
public interface IEducationVerifierService
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Interfaces;
|
||||
namespace RealCV.Application.Interfaces;
|
||||
|
||||
public interface IFileStorageService
|
||||
{
|
||||
10
src/RealCV.Application/Interfaces/IStripeService.cs
Normal file
10
src/RealCV.Application/Interfaces/IStripeService.cs
Normal file
@@ -0,0 +1,10 @@
|
||||
using RealCV.Domain.Enums;
|
||||
|
||||
namespace RealCV.Application.Interfaces;
|
||||
|
||||
public interface IStripeService
|
||||
{
|
||||
Task<string> CreateCheckoutSessionAsync(Guid userId, string email, UserPlan targetPlan, string successUrl, string cancelUrl);
|
||||
Task<string> CreateCustomerPortalSessionAsync(string stripeCustomerId, string returnUrl);
|
||||
Task HandleWebhookAsync(string json, string signature);
|
||||
}
|
||||
11
src/RealCV.Application/Interfaces/ISubscriptionService.cs
Normal file
11
src/RealCV.Application/Interfaces/ISubscriptionService.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
using RealCV.Application.DTOs;
|
||||
|
||||
namespace RealCV.Application.Interfaces;
|
||||
|
||||
public interface ISubscriptionService
|
||||
{
|
||||
Task<bool> CanPerformCheckAsync(Guid userId);
|
||||
Task IncrementUsageAsync(Guid userId);
|
||||
Task ResetUsageAsync(Guid userId);
|
||||
Task<SubscriptionInfoDto> GetSubscriptionInfoAsync(Guid userId);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using TrueCV.Application.Models;
|
||||
using RealCV.Application.Models;
|
||||
|
||||
namespace TrueCV.Application.Interfaces;
|
||||
namespace RealCV.Application.Interfaces;
|
||||
|
||||
public interface ITimelineAnalyserService
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Interfaces;
|
||||
namespace RealCV.Application.Interfaces;
|
||||
|
||||
public interface IUserContextService
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Models;
|
||||
namespace RealCV.Application.Models;
|
||||
|
||||
public sealed record CVData
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Models;
|
||||
namespace RealCV.Application.Models;
|
||||
|
||||
public sealed record CompanyVerificationResult
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Models;
|
||||
namespace RealCV.Application.Models;
|
||||
|
||||
public sealed record EducationEntry
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Models;
|
||||
namespace RealCV.Application.Models;
|
||||
|
||||
public sealed record EducationVerificationResult
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Models;
|
||||
namespace RealCV.Application.Models;
|
||||
|
||||
public sealed record EmploymentEntry
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Models;
|
||||
namespace RealCV.Application.Models;
|
||||
|
||||
public sealed record FlagResult
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Models;
|
||||
namespace RealCV.Application.Models;
|
||||
|
||||
public record SemanticMatchResult
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Models;
|
||||
namespace RealCV.Application.Models;
|
||||
|
||||
public sealed record TimelineAnalysisResult
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Models;
|
||||
namespace RealCV.Application.Models;
|
||||
|
||||
public sealed record TimelineGap
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Models;
|
||||
namespace RealCV.Application.Models;
|
||||
|
||||
public sealed record TimelineOverlap
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Application.Models;
|
||||
namespace RealCV.Application.Models;
|
||||
|
||||
public sealed record VeracityReport
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TrueCV.Domain\TrueCV.Domain.csproj" />
|
||||
<ProjectReference Include="..\RealCV.Domain\RealCV.Domain.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<PropertyGroup>
|
||||
31
src/RealCV.Domain/Constants/PlanLimits.cs
Normal file
31
src/RealCV.Domain/Constants/PlanLimits.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using RealCV.Domain.Enums;
|
||||
|
||||
namespace RealCV.Domain.Constants;
|
||||
|
||||
public static class PlanLimits
|
||||
{
|
||||
public static int GetMonthlyLimit(UserPlan plan) => plan switch
|
||||
{
|
||||
UserPlan.Free => 3,
|
||||
UserPlan.Professional => 30,
|
||||
UserPlan.Enterprise => int.MaxValue,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
public static int GetPricePence(UserPlan plan) => plan switch
|
||||
{
|
||||
UserPlan.Professional => 4900,
|
||||
UserPlan.Enterprise => 19900,
|
||||
_ => 0
|
||||
};
|
||||
|
||||
public static string GetDisplayPrice(UserPlan plan) => plan switch
|
||||
{
|
||||
UserPlan.Free => "Free",
|
||||
UserPlan.Professional => "£49/month",
|
||||
UserPlan.Enterprise => "£199/month",
|
||||
_ => "Unknown"
|
||||
};
|
||||
|
||||
public static bool IsUnlimited(UserPlan plan) => plan == UserPlan.Enterprise;
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace TrueCV.Domain.Entities;
|
||||
namespace RealCV.Domain.Entities;
|
||||
|
||||
public class AuditLog
|
||||
{
|
||||
@@ -1,7 +1,7 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using TrueCV.Domain.Enums;
|
||||
using RealCV.Domain.Enums;
|
||||
|
||||
namespace TrueCV.Domain.Entities;
|
||||
namespace RealCV.Domain.Entities;
|
||||
|
||||
public class CVCheck
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
using TrueCV.Domain.Enums;
|
||||
using RealCV.Domain.Enums;
|
||||
|
||||
namespace TrueCV.Domain.Entities;
|
||||
namespace RealCV.Domain.Entities;
|
||||
|
||||
public class CVFlag
|
||||
{
|
||||
@@ -1,6 +1,6 @@
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
|
||||
namespace TrueCV.Domain.Entities;
|
||||
namespace RealCV.Domain.Entities;
|
||||
|
||||
public class CompanyCache
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Domain.Enums;
|
||||
namespace RealCV.Domain.Enums;
|
||||
|
||||
public enum CheckStatus
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Domain.Enums;
|
||||
namespace RealCV.Domain.Enums;
|
||||
|
||||
public enum FlagCategory
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Domain.Enums;
|
||||
namespace RealCV.Domain.Enums;
|
||||
|
||||
public enum FlagSeverity
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Domain.Enums;
|
||||
namespace RealCV.Domain.Enums;
|
||||
|
||||
public enum UserPlan
|
||||
{
|
||||
19
src/RealCV.Domain/Exceptions/QuotaExceededException.cs
Normal file
19
src/RealCV.Domain/Exceptions/QuotaExceededException.cs
Normal file
@@ -0,0 +1,19 @@
|
||||
namespace RealCV.Domain.Exceptions;
|
||||
|
||||
public class QuotaExceededException : Exception
|
||||
{
|
||||
public QuotaExceededException()
|
||||
: base("Monthly CV check quota exceeded. Please upgrade your plan.")
|
||||
{
|
||||
}
|
||||
|
||||
public QuotaExceededException(string message)
|
||||
: base(message)
|
||||
{
|
||||
}
|
||||
|
||||
public QuotaExceededException(string message, Exception innerException)
|
||||
: base(message, innerException)
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Infrastructure.Configuration;
|
||||
namespace RealCV.Infrastructure.Configuration;
|
||||
|
||||
public sealed class AnthropicSettings
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Infrastructure.Configuration;
|
||||
namespace RealCV.Infrastructure.Configuration;
|
||||
|
||||
public sealed class AzureBlobSettings
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Infrastructure.Configuration;
|
||||
namespace RealCV.Infrastructure.Configuration;
|
||||
|
||||
public sealed class CompaniesHouseSettings
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Infrastructure.Configuration;
|
||||
namespace RealCV.Infrastructure.Configuration;
|
||||
|
||||
public sealed class LocalStorageSettings
|
||||
{
|
||||
17
src/RealCV.Infrastructure/Configuration/StripeSettings.cs
Normal file
17
src/RealCV.Infrastructure/Configuration/StripeSettings.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace RealCV.Infrastructure.Configuration;
|
||||
|
||||
public class StripeSettings
|
||||
{
|
||||
public const string SectionName = "Stripe";
|
||||
|
||||
public string SecretKey { get; set; } = string.Empty;
|
||||
public string PublishableKey { get; set; } = string.Empty;
|
||||
public string WebhookSecret { get; set; } = string.Empty;
|
||||
public StripePriceIds PriceIds { get; set; } = new();
|
||||
}
|
||||
|
||||
public class StripePriceIds
|
||||
{
|
||||
public string Professional { get; set; } = string.Empty;
|
||||
public string Enterprise { get; set; } = string.Empty;
|
||||
}
|
||||
@@ -1,10 +1,10 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Identity.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using TrueCV.Domain.Entities;
|
||||
using TrueCV.Infrastructure.Identity;
|
||||
using RealCV.Domain.Entities;
|
||||
using RealCV.Infrastructure.Identity;
|
||||
|
||||
namespace TrueCV.Infrastructure.Data;
|
||||
namespace RealCV.Infrastructure.Data;
|
||||
|
||||
public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityRole<Guid>, Guid>
|
||||
{
|
||||
@@ -40,6 +40,18 @@ public class ApplicationDbContext : IdentityDbContext<ApplicationUser, IdentityR
|
||||
entity.Property(u => u.StripeCustomerId)
|
||||
.HasMaxLength(256);
|
||||
|
||||
entity.Property(u => u.StripeSubscriptionId)
|
||||
.HasMaxLength(256);
|
||||
|
||||
entity.Property(u => u.SubscriptionStatus)
|
||||
.HasMaxLength(32);
|
||||
|
||||
entity.HasIndex(u => u.StripeCustomerId)
|
||||
.HasDatabaseName("IX_Users_StripeCustomerId");
|
||||
|
||||
entity.HasIndex(u => u.StripeSubscriptionId)
|
||||
.HasDatabaseName("IX_Users_StripeSubscriptionId");
|
||||
|
||||
entity.HasMany(u => u.CVChecks)
|
||||
.WithOne()
|
||||
.HasForeignKey(c => c.UserId)
|
||||
@@ -5,11 +5,11 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using TrueCV.Infrastructure.Data;
|
||||
using RealCV.Infrastructure.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TrueCV.Infrastructure.Data.Migrations
|
||||
namespace RealCV.Infrastructure.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20260118182916_InitialCreate")]
|
||||
@@ -156,7 +156,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -211,7 +211,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("CVChecks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -251,7 +251,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("CVFlags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CompanyCache", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CompanyCache", b =>
|
||||
{
|
||||
b.Property<string>("CompanyNumber")
|
||||
.HasMaxLength(32)
|
||||
@@ -281,7 +281,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("CompanyCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.User", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.User", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -307,7 +307,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("User");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -396,7 +396,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -405,7 +405,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -420,7 +420,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -429,29 +429,29 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany("CVChecks")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("TrueCV.Domain.Entities.User", null)
|
||||
b.HasOne("RealCV.Domain.Entities.User", null)
|
||||
.WithMany("CVChecks")
|
||||
.HasForeignKey("UserId1");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Domain.Entities.CVCheck", "CVCheck")
|
||||
b.HasOne("RealCV.Domain.Entities.CVCheck", "CVCheck")
|
||||
.WithMany("Flags")
|
||||
.HasForeignKey("CVCheckId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -460,17 +460,17 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.Navigation("CVCheck");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.Navigation("Flags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.User", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.User", b =>
|
||||
{
|
||||
b.Navigation("CVChecks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("CVChecks");
|
||||
});
|
||||
@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TrueCV.Infrastructure.Data.Migrations
|
||||
namespace RealCV.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class InitialCreate : Migration
|
||||
@@ -5,11 +5,11 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using TrueCV.Infrastructure.Data;
|
||||
using RealCV.Infrastructure.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TrueCV.Infrastructure.Data.Migrations
|
||||
namespace RealCV.Infrastructure.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20260120191035_AddProcessingStageToCV")]
|
||||
@@ -156,7 +156,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -210,7 +210,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("CVChecks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -250,7 +250,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("CVFlags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CompanyCache", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CompanyCache", b =>
|
||||
{
|
||||
b.Property<string>("CompanyNumber")
|
||||
.HasMaxLength(32)
|
||||
@@ -292,7 +292,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("CompanyCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -381,7 +381,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -390,7 +390,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -405,7 +405,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -414,25 +414,25 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany("CVChecks")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Domain.Entities.CVCheck", "CVCheck")
|
||||
b.HasOne("RealCV.Domain.Entities.CVCheck", "CVCheck")
|
||||
.WithMany("Flags")
|
||||
.HasForeignKey("CVCheckId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -441,12 +441,12 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.Navigation("CVCheck");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.Navigation("Flags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("CVChecks");
|
||||
});
|
||||
@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TrueCV.Infrastructure.Data.Migrations
|
||||
namespace RealCV.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddProcessingStageToCV : Migration
|
||||
@@ -5,11 +5,11 @@ using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using TrueCV.Infrastructure.Data;
|
||||
using RealCV.Infrastructure.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TrueCV.Infrastructure.Data.Migrations
|
||||
namespace RealCV.Infrastructure.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20260120194532_AddAuditLogTable")]
|
||||
@@ -156,7 +156,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.AuditLog", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.AuditLog", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -202,7 +202,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("AuditLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -256,7 +256,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("CVChecks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -296,7 +296,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("CVFlags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CompanyCache", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CompanyCache", b =>
|
||||
{
|
||||
b.Property<string>("CompanyNumber")
|
||||
.HasMaxLength(32)
|
||||
@@ -338,7 +338,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("CompanyCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -427,7 +427,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -436,7 +436,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -451,7 +451,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -460,25 +460,25 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany("CVChecks")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Domain.Entities.CVCheck", "CVCheck")
|
||||
b.HasOne("RealCV.Domain.Entities.CVCheck", "CVCheck")
|
||||
.WithMany("Flags")
|
||||
.HasForeignKey("CVCheckId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -487,12 +487,12 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.Navigation("CVCheck");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.Navigation("Flags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("CVChecks");
|
||||
});
|
||||
@@ -3,7 +3,7 @@ using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TrueCV.Infrastructure.Data.Migrations
|
||||
namespace RealCV.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddAuditLogTable : Migration
|
||||
519
src/RealCV.Infrastructure/Data/Migrations/20260121115517_AddStripeSubscriptionFields.Designer.cs
generated
Normal file
519
src/RealCV.Infrastructure/Data/Migrations/20260121115517_AddStripeSubscriptionFields.Designer.cs
generated
Normal file
@@ -0,0 +1,519 @@
|
||||
// <auto-generated />
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using RealCV.Infrastructure.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace RealCV.Infrastructure.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
[Migration("20260121115517_AddStripeSubscriptionFields")]
|
||||
partial class AddStripeSubscriptionFields
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void BuildTargetModel(ModelBuilder modelBuilder)
|
||||
{
|
||||
#pragma warning disable 612, 618
|
||||
modelBuilder
|
||||
.HasAnnotation("ProductVersion", "8.0.23")
|
||||
.HasAnnotation("Relational:MaxIdentifierLength", 128);
|
||||
|
||||
SqlServerModelBuilderExtensions.UseIdentityColumns(modelBuilder);
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("RoleNameIndex")
|
||||
.HasFilter("[NormalizedName] IS NOT NULL");
|
||||
|
||||
b.ToTable("AspNetRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("RoleId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetRoleClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.Property<int>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("int");
|
||||
|
||||
SqlServerPropertyBuilderExtensions.UseIdentityColumn(b.Property<int>("Id"));
|
||||
|
||||
b.Property<string>("ClaimType")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("ClaimValue")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserClaims", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderKey")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("ProviderDisplayName")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("LoginProvider", "ProviderKey");
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("AspNetUserLogins", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("RoleId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("UserId", "RoleId");
|
||||
|
||||
b.HasIndex("RoleId");
|
||||
|
||||
b.ToTable("AspNetUserRoles", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("LoginProvider")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Name")
|
||||
.HasColumnType("nvarchar(450)");
|
||||
|
||||
b.Property<string>("Value")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.HasKey("UserId", "LoginProvider", "Name");
|
||||
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.AuditLog", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Action")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Details")
|
||||
.HasMaxLength(1024)
|
||||
.HasColumnType("nvarchar(1024)");
|
||||
|
||||
b.Property<Guid?>("EntityId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("EntityType")
|
||||
.HasMaxLength(128)
|
||||
.HasColumnType("nvarchar(128)");
|
||||
|
||||
b.Property<string>("IpAddress")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Action")
|
||||
.HasDatabaseName("IX_AuditLogs_Action");
|
||||
|
||||
b.HasIndex("CreatedAt")
|
||||
.HasDatabaseName("IX_AuditLogs_CreatedAt");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("IX_AuditLogs_UserId");
|
||||
|
||||
b.ToTable("AuditLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("BlobUrl")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("nvarchar(2048)");
|
||||
|
||||
b.Property<DateTime?>("CompletedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<DateTime>("CreatedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("ExtractedDataJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("OriginalFileName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("ProcessingStage")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<string>("ReportJson")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<Guid>("UserId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int?>("VeracityScore")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("Status")
|
||||
.HasDatabaseName("IX_CVChecks_Status");
|
||||
|
||||
b.HasIndex("UserId")
|
||||
.HasDatabaseName("IX_CVChecks_UserId");
|
||||
|
||||
b.ToTable("CVChecks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<Guid>("CVCheckId")
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<string>("Category")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<string>("Description")
|
||||
.IsRequired()
|
||||
.HasMaxLength(2048)
|
||||
.HasColumnType("nvarchar(2048)");
|
||||
|
||||
b.Property<int>("ScoreImpact")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("Severity")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<string>("Title")
|
||||
.IsRequired()
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("CVCheckId")
|
||||
.HasDatabaseName("IX_CVFlags_CVCheckId");
|
||||
|
||||
b.ToTable("CVFlags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CompanyCache", b =>
|
||||
{
|
||||
b.Property<string>("CompanyNumber")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<string>("AccountsCategory")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateTime>("CachedAt")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("CompanyName")
|
||||
.IsRequired()
|
||||
.HasMaxLength(512)
|
||||
.HasColumnType("nvarchar(512)");
|
||||
|
||||
b.Property<string>("CompanyType")
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.Property<DateOnly?>("DissolutionDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<DateOnly?>("IncorporationDate")
|
||||
.HasColumnType("date");
|
||||
|
||||
b.Property<string>("SicCodesJson")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("Status")
|
||||
.IsRequired()
|
||||
.HasMaxLength(64)
|
||||
.HasColumnType("nvarchar(64)");
|
||||
|
||||
b.HasKey("CompanyNumber");
|
||||
|
||||
b.ToTable("CompanyCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
.HasColumnType("uniqueidentifier");
|
||||
|
||||
b.Property<int>("AccessFailedCount")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<int>("ChecksUsedThisMonth")
|
||||
.HasColumnType("int");
|
||||
|
||||
b.Property<string>("ConcurrencyStamp")
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("CurrentPeriodEnd")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<bool>("EmailConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<bool>("LockoutEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<DateTimeOffset?>("LockoutEnd")
|
||||
.HasColumnType("datetimeoffset");
|
||||
|
||||
b.Property<string>("NormalizedEmail")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("NormalizedUserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("PasswordHash")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("PhoneNumber")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<bool>("PhoneNumberConfirmed")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("Plan")
|
||||
.IsRequired()
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<string>("SecurityStamp")
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<string>("StripeCustomerId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("StripeSubscriptionId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("SubscriptionStatus")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
b.Property<string>("UserName")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.HasKey("Id");
|
||||
|
||||
b.HasIndex("NormalizedEmail")
|
||||
.HasDatabaseName("EmailIndex");
|
||||
|
||||
b.HasIndex("NormalizedUserName")
|
||||
.IsUnique()
|
||||
.HasDatabaseName("UserNameIndex")
|
||||
.HasFilter("[NormalizedUserName] IS NOT NULL");
|
||||
|
||||
b.HasIndex("StripeCustomerId")
|
||||
.HasDatabaseName("IX_Users_StripeCustomerId");
|
||||
|
||||
b.HasIndex("StripeSubscriptionId")
|
||||
.HasDatabaseName("IX_Users_StripeSubscriptionId");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityRoleClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserRole<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("Microsoft.AspNetCore.Identity.IdentityRole<System.Guid>", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("RoleId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany("CVChecks")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
|
||||
{
|
||||
b.HasOne("RealCV.Domain.Entities.CVCheck", "CVCheck")
|
||||
.WithMany("Flags")
|
||||
.HasForeignKey("CVCheckId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.Navigation("CVCheck");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.Navigation("Flags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("CVChecks");
|
||||
});
|
||||
#pragma warning restore 612, 618
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
using System;
|
||||
using Microsoft.EntityFrameworkCore.Migrations;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace RealCV.Infrastructure.Data.Migrations
|
||||
{
|
||||
/// <inheritdoc />
|
||||
public partial class AddStripeSubscriptionFields : Migration
|
||||
{
|
||||
/// <inheritdoc />
|
||||
protected override void Up(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.AddColumn<DateTime>(
|
||||
name: "CurrentPeriodEnd",
|
||||
table: "AspNetUsers",
|
||||
type: "datetime2",
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "StripeSubscriptionId",
|
||||
table: "AspNetUsers",
|
||||
type: "nvarchar(256)",
|
||||
maxLength: 256,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.AddColumn<string>(
|
||||
name: "SubscriptionStatus",
|
||||
table: "AspNetUsers",
|
||||
type: "nvarchar(32)",
|
||||
maxLength: 32,
|
||||
nullable: true);
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_StripeCustomerId",
|
||||
table: "AspNetUsers",
|
||||
column: "StripeCustomerId");
|
||||
|
||||
migrationBuilder.CreateIndex(
|
||||
name: "IX_Users_StripeSubscriptionId",
|
||||
table: "AspNetUsers",
|
||||
column: "StripeSubscriptionId");
|
||||
}
|
||||
|
||||
/// <inheritdoc />
|
||||
protected override void Down(MigrationBuilder migrationBuilder)
|
||||
{
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Users_StripeCustomerId",
|
||||
table: "AspNetUsers");
|
||||
|
||||
migrationBuilder.DropIndex(
|
||||
name: "IX_Users_StripeSubscriptionId",
|
||||
table: "AspNetUsers");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "CurrentPeriodEnd",
|
||||
table: "AspNetUsers");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "StripeSubscriptionId",
|
||||
table: "AspNetUsers");
|
||||
|
||||
migrationBuilder.DropColumn(
|
||||
name: "SubscriptionStatus",
|
||||
table: "AspNetUsers");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -4,11 +4,11 @@ using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Infrastructure;
|
||||
using Microsoft.EntityFrameworkCore.Metadata;
|
||||
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
|
||||
using TrueCV.Infrastructure.Data;
|
||||
using RealCV.Infrastructure.Data;
|
||||
|
||||
#nullable disable
|
||||
|
||||
namespace TrueCV.Infrastructure.Data.Migrations
|
||||
namespace RealCV.Infrastructure.Data.Migrations
|
||||
{
|
||||
[DbContext(typeof(ApplicationDbContext))]
|
||||
partial class ApplicationDbContextModelSnapshot : ModelSnapshot
|
||||
@@ -153,7 +153,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("AspNetUserTokens", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.AuditLog", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.AuditLog", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -199,7 +199,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("AuditLogs");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -253,7 +253,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("CVChecks");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -293,7 +293,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("CVFlags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CompanyCache", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CompanyCache", b =>
|
||||
{
|
||||
b.Property<string>("CompanyNumber")
|
||||
.HasMaxLength(32)
|
||||
@@ -335,7 +335,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.ToTable("CompanyCache");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
{
|
||||
b.Property<Guid>("Id")
|
||||
.ValueGeneratedOnAdd()
|
||||
@@ -351,6 +351,9 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
.IsConcurrencyToken()
|
||||
.HasColumnType("nvarchar(max)");
|
||||
|
||||
b.Property<DateTime?>("CurrentPeriodEnd")
|
||||
.HasColumnType("datetime2");
|
||||
|
||||
b.Property<string>("Email")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
@@ -393,6 +396,14 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("StripeSubscriptionId")
|
||||
.HasMaxLength(256)
|
||||
.HasColumnType("nvarchar(256)");
|
||||
|
||||
b.Property<string>("SubscriptionStatus")
|
||||
.HasMaxLength(32)
|
||||
.HasColumnType("nvarchar(32)");
|
||||
|
||||
b.Property<bool>("TwoFactorEnabled")
|
||||
.HasColumnType("bit");
|
||||
|
||||
@@ -410,6 +421,12 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
.HasDatabaseName("UserNameIndex")
|
||||
.HasFilter("[NormalizedUserName] IS NOT NULL");
|
||||
|
||||
b.HasIndex("StripeCustomerId")
|
||||
.HasDatabaseName("IX_Users_StripeCustomerId");
|
||||
|
||||
b.HasIndex("StripeSubscriptionId")
|
||||
.HasDatabaseName("IX_Users_StripeSubscriptionId");
|
||||
|
||||
b.ToTable("AspNetUsers", (string)null);
|
||||
});
|
||||
|
||||
@@ -424,7 +441,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserClaim<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -433,7 +450,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserLogin<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -448,7 +465,7 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -457,25 +474,25 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
|
||||
modelBuilder.Entity("Microsoft.AspNetCore.Identity.IdentityUserToken<System.Guid>", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany()
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
b.HasOne("RealCV.Infrastructure.Identity.ApplicationUser", null)
|
||||
.WithMany("CVChecks")
|
||||
.HasForeignKey("UserId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
.IsRequired();
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVFlag", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVFlag", b =>
|
||||
{
|
||||
b.HasOne("TrueCV.Domain.Entities.CVCheck", "CVCheck")
|
||||
b.HasOne("RealCV.Domain.Entities.CVCheck", "CVCheck")
|
||||
.WithMany("Flags")
|
||||
.HasForeignKey("CVCheckId")
|
||||
.OnDelete(DeleteBehavior.Cascade)
|
||||
@@ -484,12 +501,12 @@ namespace TrueCV.Infrastructure.Data.Migrations
|
||||
b.Navigation("CVCheck");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Domain.Entities.CVCheck", b =>
|
||||
modelBuilder.Entity("RealCV.Domain.Entities.CVCheck", b =>
|
||||
{
|
||||
b.Navigation("Flags");
|
||||
});
|
||||
|
||||
modelBuilder.Entity("TrueCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
modelBuilder.Entity("RealCV.Infrastructure.Identity.ApplicationUser", b =>
|
||||
{
|
||||
b.Navigation("CVChecks");
|
||||
});
|
||||
@@ -5,14 +5,15 @@ using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using Polly;
|
||||
using Polly.Extensions.Http;
|
||||
using TrueCV.Application.Interfaces;
|
||||
using TrueCV.Infrastructure.Configuration;
|
||||
using TrueCV.Infrastructure.Data;
|
||||
using TrueCV.Infrastructure.ExternalApis;
|
||||
using TrueCV.Infrastructure.Jobs;
|
||||
using TrueCV.Infrastructure.Services;
|
||||
using Stripe;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Infrastructure.Configuration;
|
||||
using RealCV.Infrastructure.Data;
|
||||
using RealCV.Infrastructure.ExternalApis;
|
||||
using RealCV.Infrastructure.Jobs;
|
||||
using RealCV.Infrastructure.Services;
|
||||
|
||||
namespace TrueCV.Infrastructure;
|
||||
namespace RealCV.Infrastructure;
|
||||
|
||||
public static class DependencyInjection
|
||||
{
|
||||
@@ -74,6 +75,16 @@ public static class DependencyInjection
|
||||
services.Configure<LocalStorageSettings>(
|
||||
configuration.GetSection(LocalStorageSettings.SectionName));
|
||||
|
||||
services.Configure<StripeSettings>(
|
||||
configuration.GetSection(StripeSettings.SectionName));
|
||||
|
||||
// Configure Stripe API key
|
||||
var stripeSettings = configuration.GetSection(StripeSettings.SectionName).Get<StripeSettings>();
|
||||
if (!string.IsNullOrEmpty(stripeSettings?.SecretKey))
|
||||
{
|
||||
StripeConfiguration.ApiKey = stripeSettings.SecretKey;
|
||||
}
|
||||
|
||||
// Configure HttpClient for CompaniesHouseClient with retry policy
|
||||
services.AddHttpClient<CompaniesHouseClient>((serviceProvider, client) =>
|
||||
{
|
||||
@@ -97,6 +108,8 @@ public static class DependencyInjection
|
||||
services.AddScoped<ICVCheckService, CVCheckService>();
|
||||
services.AddScoped<IUserContextService, UserContextService>();
|
||||
services.AddScoped<IAuditService, AuditService>();
|
||||
services.AddScoped<IStripeService, StripeService>();
|
||||
services.AddScoped<ISubscriptionService, Services.SubscriptionService>();
|
||||
|
||||
// Register file storage - use local storage if configured, otherwise Azure
|
||||
var useLocalStorage = configuration.GetValue<bool>("UseLocalStorage");
|
||||
@@ -111,6 +124,7 @@ public static class DependencyInjection
|
||||
|
||||
// Register Hangfire jobs
|
||||
services.AddTransient<ProcessCVCheckJob>();
|
||||
services.AddTransient<ResetMonthlyUsageJob>();
|
||||
|
||||
return services;
|
||||
}
|
||||
@@ -6,10 +6,10 @@ using System.Text.Json;
|
||||
using System.Text.Json.Serialization;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TrueCV.Application.DTOs;
|
||||
using TrueCV.Infrastructure.Configuration;
|
||||
using RealCV.Application.DTOs;
|
||||
using RealCV.Infrastructure.Configuration;
|
||||
|
||||
namespace TrueCV.Infrastructure.ExternalApis;
|
||||
namespace RealCV.Infrastructure.ExternalApis;
|
||||
|
||||
public sealed class CompaniesHouseClient
|
||||
{
|
||||
@@ -1,4 +1,4 @@
|
||||
namespace TrueCV.Infrastructure.Helpers;
|
||||
namespace RealCV.Infrastructure.Helpers;
|
||||
|
||||
/// <summary>
|
||||
/// Helper methods for processing AI/LLM JSON responses.
|
||||
@@ -1,8 +1,8 @@
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using TrueCV.Domain.Entities;
|
||||
using TrueCV.Domain.Enums;
|
||||
using RealCV.Domain.Entities;
|
||||
using RealCV.Domain.Enums;
|
||||
|
||||
namespace TrueCV.Infrastructure.Identity;
|
||||
namespace RealCV.Infrastructure.Identity;
|
||||
|
||||
public class ApplicationUser : IdentityUser<Guid>
|
||||
{
|
||||
@@ -10,6 +10,12 @@ public class ApplicationUser : IdentityUser<Guid>
|
||||
|
||||
public string? StripeCustomerId { get; set; }
|
||||
|
||||
public string? StripeSubscriptionId { get; set; }
|
||||
|
||||
public string? SubscriptionStatus { get; set; }
|
||||
|
||||
public DateTime? CurrentPeriodEnd { get; set; }
|
||||
|
||||
public int ChecksUsedThisMonth { get; set; }
|
||||
|
||||
public ICollection<CVCheck> CVChecks { get; set; } = new List<CVCheck>();
|
||||
106
src/RealCV.Infrastructure/Jobs/DataRetentionJob.cs
Normal file
106
src/RealCV.Infrastructure/Jobs/DataRetentionJob.cs
Normal file
@@ -0,0 +1,106 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Configuration;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Domain.Enums;
|
||||
using RealCV.Infrastructure.Data;
|
||||
|
||||
namespace RealCV.Infrastructure.Jobs;
|
||||
|
||||
/// <summary>
|
||||
/// GDPR compliance job that automatically deletes old CV check data
|
||||
/// based on configured retention period.
|
||||
/// </summary>
|
||||
public sealed class DataRetentionJob
|
||||
{
|
||||
private readonly ApplicationDbContext _dbContext;
|
||||
private readonly IFileStorageService _fileStorageService;
|
||||
private readonly ILogger<DataRetentionJob> _logger;
|
||||
private readonly int _retentionDays;
|
||||
|
||||
public DataRetentionJob(
|
||||
ApplicationDbContext dbContext,
|
||||
IFileStorageService fileStorageService,
|
||||
IConfiguration configuration,
|
||||
ILogger<DataRetentionJob> logger)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_fileStorageService = fileStorageService;
|
||||
_logger = logger;
|
||||
_retentionDays = configuration.GetValue<int>("DataRetention:CVCheckRetentionDays", 30);
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogInformation("Starting GDPR data retention job (retention: {Days} days)", _retentionDays);
|
||||
|
||||
try
|
||||
{
|
||||
var cutoffDate = DateTime.UtcNow.AddDays(-_retentionDays);
|
||||
|
||||
// Find old completed CV checks that should be deleted
|
||||
var oldChecks = await _dbContext.CVChecks
|
||||
.Include(c => c.Flags)
|
||||
.Where(c => c.CompletedAt != null && c.CompletedAt < cutoffDate)
|
||||
.Where(c => c.Status == CheckStatus.Completed || c.Status == CheckStatus.Failed)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
if (oldChecks.Count == 0)
|
||||
{
|
||||
_logger.LogInformation("No CV checks older than {Days} days found for deletion", _retentionDays);
|
||||
return;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Found {Count} CV checks older than {Days} days for deletion", oldChecks.Count, _retentionDays);
|
||||
|
||||
var deletedCount = 0;
|
||||
var fileDeletedCount = 0;
|
||||
|
||||
foreach (var check in oldChecks)
|
||||
{
|
||||
try
|
||||
{
|
||||
// Delete any remaining files (should already be deleted after processing, but be thorough)
|
||||
if (!string.IsNullOrWhiteSpace(check.BlobUrl))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _fileStorageService.DeleteAsync(check.BlobUrl);
|
||||
fileDeletedCount++;
|
||||
_logger.LogDebug("Deleted orphaned file for CV check {CheckId}", check.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to delete file for CV check {CheckId}", check.Id);
|
||||
}
|
||||
}
|
||||
|
||||
// Delete associated flags
|
||||
_dbContext.CVFlags.RemoveRange(check.Flags);
|
||||
|
||||
// Delete the CV check record
|
||||
_dbContext.CVChecks.Remove(check);
|
||||
deletedCount++;
|
||||
|
||||
_logger.LogDebug("Marked CV check {CheckId} for deletion (created: {Created})",
|
||||
check.Id, check.CreatedAt);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error processing CV check {CheckId} for deletion", check.Id);
|
||||
}
|
||||
}
|
||||
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"GDPR data retention job completed. Deleted {DeletedCount} CV checks and {FileCount} orphaned files",
|
||||
deletedCount, fileDeletedCount);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in GDPR data retention job");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,14 @@
|
||||
using System.Text.Json;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TrueCV.Application.Helpers;
|
||||
using TrueCV.Application.Interfaces;
|
||||
using TrueCV.Application.Models;
|
||||
using TrueCV.Domain.Entities;
|
||||
using TrueCV.Domain.Enums;
|
||||
using TrueCV.Infrastructure.Data;
|
||||
using RealCV.Application.Helpers;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Application.Models;
|
||||
using RealCV.Domain.Entities;
|
||||
using RealCV.Domain.Enums;
|
||||
using RealCV.Infrastructure.Data;
|
||||
|
||||
namespace TrueCV.Infrastructure.Jobs;
|
||||
namespace RealCV.Infrastructure.Jobs;
|
||||
|
||||
public sealed class ProcessCVCheckJob
|
||||
{
|
||||
@@ -265,6 +265,12 @@ public sealed class ProcessCVCheckJob
|
||||
cvCheckId, score);
|
||||
|
||||
await _auditService.LogAsync(cvCheck.UserId, AuditActions.CVProcessed, "CVCheck", cvCheckId, $"Score: {score}");
|
||||
|
||||
// GDPR: Delete the uploaded CV file immediately after processing
|
||||
// We only need the extracted data and report, not the original file
|
||||
await DeleteCVFileAsync(cvCheck.BlobUrl, cvCheckId);
|
||||
cvCheck.BlobUrl = string.Empty; // Clear the URL as file no longer exists
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -287,6 +293,29 @@ public sealed class ProcessCVCheckJob
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// GDPR: Safely delete the uploaded CV file after processing.
|
||||
/// </summary>
|
||||
private async Task DeleteCVFileAsync(string blobUrl, Guid cvCheckId)
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(blobUrl))
|
||||
{
|
||||
_logger.LogDebug("No file to delete for CV check {CheckId}", cvCheckId);
|
||||
return;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
await _fileStorageService.DeleteAsync(blobUrl);
|
||||
_logger.LogInformation("GDPR: Deleted CV file for check {CheckId}", cvCheckId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
// Log but don't fail the job - file deletion is important but shouldn't break processing
|
||||
_logger.LogWarning(ex, "Failed to delete CV file for check {CheckId}: {BlobUrl}", cvCheckId, blobUrl);
|
||||
}
|
||||
}
|
||||
|
||||
private static (int Score, List<FlagResult> Flags) CalculateVeracityScore(
|
||||
List<CompanyVerificationResult> verifications,
|
||||
List<EducationVerificationResult> educationResults,
|
||||
@@ -398,8 +427,8 @@ public sealed class ProcessCVCheckJob
|
||||
{
|
||||
Category = FlagCategory.Education.ToString(),
|
||||
Severity = FlagSeverity.Critical.ToString(),
|
||||
Title = "Diploma Mill Detected",
|
||||
Description = $"'{edu.ClaimedInstitution}' is a known diploma mill. {edu.VerificationNotes}",
|
||||
Title = "Unaccredited Institution",
|
||||
Description = $"'{edu.ClaimedInstitution}' was not found in accredited institutions databases. Manual verification recommended.",
|
||||
ScoreImpact = -DiplomaMillPenalty
|
||||
});
|
||||
}
|
||||
@@ -413,8 +442,8 @@ public sealed class ProcessCVCheckJob
|
||||
{
|
||||
Category = FlagCategory.Education.ToString(),
|
||||
Severity = FlagSeverity.Warning.ToString(),
|
||||
Title = "Suspicious Institution",
|
||||
Description = $"'{edu.ClaimedInstitution}' has suspicious characteristics. {edu.VerificationNotes}",
|
||||
Title = "Unrecognised Institution",
|
||||
Description = $"'{edu.ClaimedInstitution}' was not found in recognised institutions databases. Manual verification recommended.",
|
||||
ScoreImpact = -SuspiciousInstitutionPenalty
|
||||
});
|
||||
}
|
||||
80
src/RealCV.Infrastructure/Jobs/ResetMonthlyUsageJob.cs
Normal file
80
src/RealCV.Infrastructure/Jobs/ResetMonthlyUsageJob.cs
Normal file
@@ -0,0 +1,80 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RealCV.Domain.Enums;
|
||||
using RealCV.Infrastructure.Data;
|
||||
|
||||
namespace RealCV.Infrastructure.Jobs;
|
||||
|
||||
/// <summary>
|
||||
/// Hangfire job that resets monthly CV check usage for users whose billing period has ended.
|
||||
/// This job should run daily to catch users whose subscriptions renewed.
|
||||
/// </summary>
|
||||
public sealed class ResetMonthlyUsageJob
|
||||
{
|
||||
private readonly ApplicationDbContext _dbContext;
|
||||
private readonly ILogger<ResetMonthlyUsageJob> _logger;
|
||||
|
||||
public ResetMonthlyUsageJob(
|
||||
ApplicationDbContext dbContext,
|
||||
ILogger<ResetMonthlyUsageJob> logger)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task ExecuteAsync(CancellationToken cancellationToken = default)
|
||||
{
|
||||
_logger.LogInformation("Starting monthly usage reset job");
|
||||
|
||||
try
|
||||
{
|
||||
var now = DateTime.UtcNow;
|
||||
|
||||
// Reset usage for paid users whose billing period has ended
|
||||
// The webhook handler already resets usage when subscription renews,
|
||||
// but this catches any edge cases or delays in webhook delivery
|
||||
var paidUsersToReset = await _dbContext.Users
|
||||
.Where(u => u.Plan != UserPlan.Free)
|
||||
.Where(u => u.CurrentPeriodEnd != null && u.CurrentPeriodEnd <= now)
|
||||
.Where(u => u.ChecksUsedThisMonth > 0)
|
||||
.Where(u => u.SubscriptionStatus == "active")
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var user in paidUsersToReset)
|
||||
{
|
||||
user.ChecksUsedThisMonth = 0;
|
||||
_logger.LogInformation(
|
||||
"Reset usage for paid user {UserId} - billing period ended {PeriodEnd}",
|
||||
user.Id, user.CurrentPeriodEnd);
|
||||
}
|
||||
|
||||
// Reset usage for free users on the 1st of each month
|
||||
if (now.Day == 1)
|
||||
{
|
||||
var freeUsersToReset = await _dbContext.Users
|
||||
.Where(u => u.Plan == UserPlan.Free)
|
||||
.Where(u => u.ChecksUsedThisMonth > 0)
|
||||
.ToListAsync(cancellationToken);
|
||||
|
||||
foreach (var user in freeUsersToReset)
|
||||
{
|
||||
user.ChecksUsedThisMonth = 0;
|
||||
_logger.LogInformation("Reset usage for free user {UserId}", user.Id);
|
||||
}
|
||||
|
||||
_logger.LogInformation("Reset usage for {Count} free users", freeUsersToReset.Count);
|
||||
}
|
||||
|
||||
await _dbContext.SaveChangesAsync(cancellationToken);
|
||||
|
||||
_logger.LogInformation(
|
||||
"Monthly usage reset job completed. Reset {PaidCount} paid users",
|
||||
paidUsersToReset.Count);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "Error in monthly usage reset job");
|
||||
throw;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,7 +1,7 @@
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TrueCV.Application\TrueCV.Application.csproj" />
|
||||
<ProjectReference Include="..\RealCV.Application\RealCV.Application.csproj" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
@@ -19,6 +19,7 @@
|
||||
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="8.0.*" />
|
||||
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="8.0.*" />
|
||||
<PackageReference Include="Serilog.AspNetCore" Version="10.0.0" />
|
||||
<PackageReference Include="Stripe.net" Version="50.2.0" />
|
||||
<PackageReference Include="UglyToad.PdfPig" Version="1.7.0-custom-5" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -3,13 +3,13 @@ using Anthropic.SDK;
|
||||
using Anthropic.SDK.Messaging;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TrueCV.Application.Helpers;
|
||||
using TrueCV.Application.Interfaces;
|
||||
using TrueCV.Application.Models;
|
||||
using TrueCV.Infrastructure.Configuration;
|
||||
using TrueCV.Infrastructure.Helpers;
|
||||
using RealCV.Application.Helpers;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Application.Models;
|
||||
using RealCV.Infrastructure.Configuration;
|
||||
using RealCV.Infrastructure.Helpers;
|
||||
|
||||
namespace TrueCV.Infrastructure.Services;
|
||||
namespace RealCV.Infrastructure.Services;
|
||||
|
||||
public sealed class AICompanyNameMatcherService : ICompanyNameMatcherService
|
||||
{
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TrueCV.Application.Interfaces;
|
||||
using TrueCV.Domain.Entities;
|
||||
using TrueCV.Infrastructure.Data;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Domain.Entities;
|
||||
using RealCV.Infrastructure.Data;
|
||||
|
||||
namespace TrueCV.Infrastructure.Services;
|
||||
namespace RealCV.Infrastructure.Services;
|
||||
|
||||
public sealed class AuditService : IAuditService
|
||||
{
|
||||
@@ -2,16 +2,17 @@ using System.Text.Json;
|
||||
using Hangfire;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TrueCV.Application.DTOs;
|
||||
using TrueCV.Application.Helpers;
|
||||
using TrueCV.Application.Interfaces;
|
||||
using TrueCV.Application.Models;
|
||||
using TrueCV.Domain.Entities;
|
||||
using TrueCV.Domain.Enums;
|
||||
using TrueCV.Infrastructure.Data;
|
||||
using TrueCV.Infrastructure.Jobs;
|
||||
using RealCV.Application.DTOs;
|
||||
using RealCV.Application.Helpers;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Application.Models;
|
||||
using RealCV.Domain.Entities;
|
||||
using RealCV.Domain.Enums;
|
||||
using RealCV.Domain.Exceptions;
|
||||
using RealCV.Infrastructure.Data;
|
||||
using RealCV.Infrastructure.Jobs;
|
||||
|
||||
namespace TrueCV.Infrastructure.Services;
|
||||
namespace RealCV.Infrastructure.Services;
|
||||
|
||||
public sealed class CVCheckService : ICVCheckService
|
||||
{
|
||||
@@ -19,6 +20,7 @@ public sealed class CVCheckService : ICVCheckService
|
||||
private readonly IFileStorageService _fileStorageService;
|
||||
private readonly IBackgroundJobClient _backgroundJobClient;
|
||||
private readonly IAuditService _auditService;
|
||||
private readonly ISubscriptionService _subscriptionService;
|
||||
private readonly ILogger<CVCheckService> _logger;
|
||||
|
||||
public CVCheckService(
|
||||
@@ -26,12 +28,14 @@ public sealed class CVCheckService : ICVCheckService
|
||||
IFileStorageService fileStorageService,
|
||||
IBackgroundJobClient backgroundJobClient,
|
||||
IAuditService auditService,
|
||||
ISubscriptionService subscriptionService,
|
||||
ILogger<CVCheckService> logger)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_fileStorageService = fileStorageService;
|
||||
_backgroundJobClient = backgroundJobClient;
|
||||
_auditService = auditService;
|
||||
_subscriptionService = subscriptionService;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
@@ -42,6 +46,13 @@ public sealed class CVCheckService : ICVCheckService
|
||||
|
||||
_logger.LogDebug("Creating CV check for user {UserId}, file: {FileName}", userId, fileName);
|
||||
|
||||
// Check quota before proceeding
|
||||
if (!await _subscriptionService.CanPerformCheckAsync(userId))
|
||||
{
|
||||
_logger.LogWarning("User {UserId} quota exceeded - CV check denied", userId);
|
||||
throw new QuotaExceededException();
|
||||
}
|
||||
|
||||
// Upload file to blob storage
|
||||
var blobUrl = await _fileStorageService.UploadAsync(file, fileName);
|
||||
|
||||
@@ -71,6 +82,9 @@ public sealed class CVCheckService : ICVCheckService
|
||||
|
||||
await _auditService.LogAsync(userId, AuditActions.CVUploaded, "CVCheck", cvCheck.Id, $"File: {fileName}");
|
||||
|
||||
// Increment usage after successful creation
|
||||
await _subscriptionService.IncrementUsageAsync(userId);
|
||||
|
||||
return cvCheck.Id;
|
||||
}
|
||||
|
||||
@@ -171,17 +185,78 @@ public sealed class CVCheckService : ICVCheckService
|
||||
|
||||
var fileName = cvCheck.OriginalFileName;
|
||||
|
||||
// GDPR: Delete the uploaded file if it still exists
|
||||
if (!string.IsNullOrWhiteSpace(cvCheck.BlobUrl))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _fileStorageService.DeleteAsync(cvCheck.BlobUrl);
|
||||
_logger.LogDebug("Deleted file for CV check {CheckId}", checkId);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to delete file for CV check {CheckId}", checkId);
|
||||
// Continue with deletion even if file deletion fails
|
||||
}
|
||||
}
|
||||
|
||||
_dbContext.CVFlags.RemoveRange(cvCheck.Flags);
|
||||
_dbContext.CVChecks.Remove(cvCheck);
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Deleted CV check {CheckId} for user {UserId}", checkId, userId);
|
||||
_logger.LogInformation("GDPR: Deleted CV check {CheckId} and associated data for user {UserId}", checkId, userId);
|
||||
|
||||
await _auditService.LogAsync(userId, AuditActions.CVDeleted, "CVCheck", checkId, $"File: {fileName}");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task<int> DeleteAllUserDataAsync(Guid userId)
|
||||
{
|
||||
_logger.LogInformation("GDPR: Deleting all CV data for user {UserId}", userId);
|
||||
|
||||
var userChecks = await _dbContext.CVChecks
|
||||
.Include(c => c.Flags)
|
||||
.Where(c => c.UserId == userId)
|
||||
.ToListAsync();
|
||||
|
||||
if (userChecks.Count == 0)
|
||||
{
|
||||
_logger.LogDebug("No CV checks found for user {UserId}", userId);
|
||||
return 0;
|
||||
}
|
||||
|
||||
var deletedCount = 0;
|
||||
|
||||
foreach (var check in userChecks)
|
||||
{
|
||||
// Delete the file if it exists
|
||||
if (!string.IsNullOrWhiteSpace(check.BlobUrl))
|
||||
{
|
||||
try
|
||||
{
|
||||
await _fileStorageService.DeleteAsync(check.BlobUrl);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogWarning(ex, "Failed to delete file for CV check {CheckId}", check.Id);
|
||||
}
|
||||
}
|
||||
|
||||
_dbContext.CVFlags.RemoveRange(check.Flags);
|
||||
_dbContext.CVChecks.Remove(check);
|
||||
deletedCount++;
|
||||
}
|
||||
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("GDPR: Deleted {Count} CV checks for user {UserId}", deletedCount, userId);
|
||||
|
||||
await _auditService.LogAsync(userId, AuditActions.CVDeleted, null, null, $"Deleted all data: {deletedCount} checks");
|
||||
|
||||
return deletedCount;
|
||||
}
|
||||
|
||||
private static CVCheckDto MapToDto(CVCheck cvCheck)
|
||||
{
|
||||
return new CVCheckDto
|
||||
@@ -6,14 +6,14 @@ using DocumentFormat.OpenXml.Packaging;
|
||||
using DocumentFormat.OpenXml.Wordprocessing;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TrueCV.Application.Helpers;
|
||||
using TrueCV.Application.Interfaces;
|
||||
using TrueCV.Application.Models;
|
||||
using TrueCV.Infrastructure.Configuration;
|
||||
using TrueCV.Infrastructure.Helpers;
|
||||
using RealCV.Application.Helpers;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Application.Models;
|
||||
using RealCV.Infrastructure.Configuration;
|
||||
using RealCV.Infrastructure.Helpers;
|
||||
using UglyToad.PdfPig;
|
||||
|
||||
namespace TrueCV.Infrastructure.Services;
|
||||
namespace RealCV.Infrastructure.Services;
|
||||
|
||||
public sealed class CVParserService : ICVParserService
|
||||
{
|
||||
@@ -2,15 +2,15 @@ using System.Text.Json;
|
||||
using FuzzySharp;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TrueCV.Application.DTOs;
|
||||
using TrueCV.Application.Helpers;
|
||||
using TrueCV.Application.Interfaces;
|
||||
using TrueCV.Application.Models;
|
||||
using TrueCV.Domain.Entities;
|
||||
using TrueCV.Infrastructure.Data;
|
||||
using TrueCV.Infrastructure.ExternalApis;
|
||||
using RealCV.Application.DTOs;
|
||||
using RealCV.Application.Helpers;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Application.Models;
|
||||
using RealCV.Domain.Entities;
|
||||
using RealCV.Infrastructure.Data;
|
||||
using RealCV.Infrastructure.ExternalApis;
|
||||
|
||||
namespace TrueCV.Infrastructure.Services;
|
||||
namespace RealCV.Infrastructure.Services;
|
||||
|
||||
public sealed class CompanyVerifierService : ICompanyVerifierService
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
using TrueCV.Application.Data;
|
||||
using TrueCV.Application.Interfaces;
|
||||
using TrueCV.Application.Models;
|
||||
using RealCV.Application.Data;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Application.Models;
|
||||
|
||||
namespace TrueCV.Infrastructure.Services;
|
||||
namespace RealCV.Infrastructure.Services;
|
||||
|
||||
public sealed class EducationVerifierService : IEducationVerifierService
|
||||
{
|
||||
@@ -24,7 +24,7 @@ public sealed class EducationVerifierService : IEducationVerifierService
|
||||
IsVerified = false,
|
||||
IsDiplomaMill = true,
|
||||
IsSuspicious = true,
|
||||
VerificationNotes = "Institution is on the diploma mill blacklist",
|
||||
VerificationNotes = "Institution not found in accredited institutions database",
|
||||
ClaimedStartDate = education.StartDate,
|
||||
ClaimedEndDate = education.EndDate,
|
||||
DatesArePlausible = true,
|
||||
@@ -43,7 +43,7 @@ public sealed class EducationVerifierService : IEducationVerifierService
|
||||
IsVerified = false,
|
||||
IsDiplomaMill = false,
|
||||
IsSuspicious = true,
|
||||
VerificationNotes = "Institution name contains suspicious patterns common in diploma mills",
|
||||
VerificationNotes = "Institution not found in recognised institutions database",
|
||||
ClaimedStartDate = education.StartDate,
|
||||
ClaimedEndDate = education.EndDate,
|
||||
DatesArePlausible = true,
|
||||
@@ -2,10 +2,10 @@ using Azure.Storage.Blobs;
|
||||
using Azure.Storage.Blobs.Models;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TrueCV.Application.Interfaces;
|
||||
using TrueCV.Infrastructure.Configuration;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Infrastructure.Configuration;
|
||||
|
||||
namespace TrueCV.Infrastructure.Services;
|
||||
namespace RealCV.Infrastructure.Services;
|
||||
|
||||
public sealed class FileStorageService : IFileStorageService
|
||||
{
|
||||
@@ -1,9 +1,9 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using TrueCV.Application.Interfaces;
|
||||
using TrueCV.Infrastructure.Configuration;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Infrastructure.Configuration;
|
||||
|
||||
namespace TrueCV.Infrastructure.Services;
|
||||
namespace RealCV.Infrastructure.Services;
|
||||
|
||||
public sealed class LocalFileStorageService : IFileStorageService
|
||||
{
|
||||
316
src/RealCV.Infrastructure/Services/StripeService.cs
Normal file
316
src/RealCV.Infrastructure/Services/StripeService.cs
Normal file
@@ -0,0 +1,316 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Stripe;
|
||||
using Stripe.Checkout;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Domain.Enums;
|
||||
using RealCV.Infrastructure.Configuration;
|
||||
using RealCV.Infrastructure.Data;
|
||||
|
||||
namespace RealCV.Infrastructure.Services;
|
||||
|
||||
public sealed class StripeService : IStripeService
|
||||
{
|
||||
private readonly ApplicationDbContext _dbContext;
|
||||
private readonly StripeSettings _settings;
|
||||
private readonly ILogger<StripeService> _logger;
|
||||
|
||||
public StripeService(
|
||||
ApplicationDbContext dbContext,
|
||||
IOptions<StripeSettings> settings,
|
||||
ILogger<StripeService> logger)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_settings = settings.Value;
|
||||
_logger = logger;
|
||||
|
||||
StripeConfiguration.ApiKey = _settings.SecretKey;
|
||||
}
|
||||
|
||||
public async Task<string> CreateCheckoutSessionAsync(
|
||||
Guid userId,
|
||||
string email,
|
||||
UserPlan targetPlan,
|
||||
string successUrl,
|
||||
string cancelUrl)
|
||||
{
|
||||
_logger.LogInformation("Creating checkout session for user {UserId}, plan {Plan}", userId, targetPlan);
|
||||
|
||||
var priceId = targetPlan switch
|
||||
{
|
||||
UserPlan.Professional => _settings.PriceIds.Professional,
|
||||
UserPlan.Enterprise => _settings.PriceIds.Enterprise,
|
||||
_ => throw new ArgumentException($"Invalid plan for checkout: {targetPlan}")
|
||||
};
|
||||
|
||||
if (string.IsNullOrEmpty(priceId))
|
||||
{
|
||||
throw new InvalidOperationException($"Price ID not configured for plan: {targetPlan}");
|
||||
}
|
||||
|
||||
var user = await _dbContext.Users.FindAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
throw new InvalidOperationException($"User not found: {userId}");
|
||||
}
|
||||
|
||||
var sessionOptions = new SessionCreateOptions
|
||||
{
|
||||
Mode = "subscription",
|
||||
CustomerEmail = string.IsNullOrEmpty(user.StripeCustomerId) ? email : null,
|
||||
Customer = !string.IsNullOrEmpty(user.StripeCustomerId) ? user.StripeCustomerId : null,
|
||||
LineItems = new List<SessionLineItemOptions>
|
||||
{
|
||||
new()
|
||||
{
|
||||
Price = priceId,
|
||||
Quantity = 1
|
||||
}
|
||||
},
|
||||
SuccessUrl = successUrl + "?session_id={CHECKOUT_SESSION_ID}",
|
||||
CancelUrl = cancelUrl,
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
{ "user_id", userId.ToString() },
|
||||
{ "target_plan", targetPlan.ToString() }
|
||||
},
|
||||
SubscriptionData = new SessionSubscriptionDataOptions
|
||||
{
|
||||
Metadata = new Dictionary<string, string>
|
||||
{
|
||||
{ "user_id", userId.ToString() },
|
||||
{ "plan", targetPlan.ToString() }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var sessionService = new SessionService();
|
||||
var session = await sessionService.CreateAsync(sessionOptions);
|
||||
|
||||
_logger.LogInformation("Checkout session created: {SessionId}", session.Id);
|
||||
|
||||
return session.Url;
|
||||
}
|
||||
|
||||
public async Task<string> CreateCustomerPortalSessionAsync(string stripeCustomerId, string returnUrl)
|
||||
{
|
||||
_logger.LogInformation("Creating customer portal session for customer {CustomerId}", stripeCustomerId);
|
||||
|
||||
var options = new Stripe.BillingPortal.SessionCreateOptions
|
||||
{
|
||||
Customer = stripeCustomerId,
|
||||
ReturnUrl = returnUrl
|
||||
};
|
||||
|
||||
var service = new Stripe.BillingPortal.SessionService();
|
||||
var session = await service.CreateAsync(options);
|
||||
|
||||
return session.Url;
|
||||
}
|
||||
|
||||
public async Task HandleWebhookAsync(string json, string signature)
|
||||
{
|
||||
Event stripeEvent;
|
||||
|
||||
try
|
||||
{
|
||||
stripeEvent = EventUtility.ConstructEvent(json, signature, _settings.WebhookSecret);
|
||||
}
|
||||
catch (StripeException ex)
|
||||
{
|
||||
_logger.LogError(ex, "Webhook signature verification failed");
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.LogInformation("Processing webhook event: {EventType} ({EventId})", stripeEvent.Type, stripeEvent.Id);
|
||||
|
||||
switch (stripeEvent.Type)
|
||||
{
|
||||
case EventTypes.CheckoutSessionCompleted:
|
||||
await HandleCheckoutSessionCompleted(stripeEvent);
|
||||
break;
|
||||
|
||||
case EventTypes.CustomerSubscriptionUpdated:
|
||||
await HandleSubscriptionUpdated(stripeEvent);
|
||||
break;
|
||||
|
||||
case EventTypes.CustomerSubscriptionDeleted:
|
||||
await HandleSubscriptionDeleted(stripeEvent);
|
||||
break;
|
||||
|
||||
case EventTypes.InvoicePaymentFailed:
|
||||
await HandlePaymentFailed(stripeEvent);
|
||||
break;
|
||||
|
||||
default:
|
||||
_logger.LogDebug("Unhandled webhook event type: {EventType}", stripeEvent.Type);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task HandleCheckoutSessionCompleted(Event stripeEvent)
|
||||
{
|
||||
var session = stripeEvent.Data.Object as Session;
|
||||
if (session == null)
|
||||
{
|
||||
_logger.LogWarning("Could not parse checkout session from event");
|
||||
return;
|
||||
}
|
||||
|
||||
var userIdString = session.Metadata.GetValueOrDefault("user_id");
|
||||
var targetPlanString = session.Metadata.GetValueOrDefault("target_plan");
|
||||
|
||||
if (string.IsNullOrEmpty(userIdString) || !Guid.TryParse(userIdString, out var userId))
|
||||
{
|
||||
_logger.LogWarning("Missing or invalid user_id in checkout session metadata");
|
||||
return;
|
||||
}
|
||||
|
||||
if (string.IsNullOrEmpty(targetPlanString) || !Enum.TryParse<UserPlan>(targetPlanString, out var targetPlan))
|
||||
{
|
||||
_logger.LogWarning("Missing or invalid target_plan in checkout session metadata");
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await _dbContext.Users.FindAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("User not found for checkout session: {UserId}", userId);
|
||||
return;
|
||||
}
|
||||
|
||||
user.StripeCustomerId = session.CustomerId;
|
||||
user.StripeSubscriptionId = session.SubscriptionId;
|
||||
user.Plan = targetPlan;
|
||||
user.SubscriptionStatus = "active";
|
||||
user.ChecksUsedThisMonth = 0;
|
||||
|
||||
// Fetch subscription to get period end (from the first item)
|
||||
if (!string.IsNullOrEmpty(session.SubscriptionId))
|
||||
{
|
||||
var stripeSubscriptionService = new Stripe.SubscriptionService();
|
||||
var stripeSubscription = await stripeSubscriptionService.GetAsync(session.SubscriptionId);
|
||||
var firstItem = stripeSubscription.Items?.Data?.FirstOrDefault();
|
||||
if (firstItem != null)
|
||||
{
|
||||
user.CurrentPeriodEnd = firstItem.CurrentPeriodEnd;
|
||||
}
|
||||
}
|
||||
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation(
|
||||
"User {UserId} upgraded to {Plan} via checkout session {SessionId}",
|
||||
userId, targetPlan, session.Id);
|
||||
}
|
||||
|
||||
private async Task HandleSubscriptionUpdated(Event stripeEvent)
|
||||
{
|
||||
var stripeSubscription = stripeEvent.Data.Object as Stripe.Subscription;
|
||||
if (stripeSubscription == null)
|
||||
{
|
||||
_logger.LogWarning("Could not parse subscription from event");
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await _dbContext.Users
|
||||
.FirstOrDefaultAsync(u => u.StripeSubscriptionId == stripeSubscription.Id);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogDebug("No user found for subscription: {SubscriptionId}", stripeSubscription.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
var previousStatus = user.SubscriptionStatus;
|
||||
var previousPeriodEnd = user.CurrentPeriodEnd;
|
||||
|
||||
user.SubscriptionStatus = stripeSubscription.Status;
|
||||
|
||||
// Get period end from first subscription item
|
||||
var firstItem = stripeSubscription.Items?.Data?.FirstOrDefault();
|
||||
var newPeriodEnd = firstItem?.CurrentPeriodEnd;
|
||||
user.CurrentPeriodEnd = newPeriodEnd;
|
||||
|
||||
// Reset usage if billing period renewed
|
||||
if (previousPeriodEnd.HasValue &&
|
||||
newPeriodEnd.HasValue &&
|
||||
newPeriodEnd.Value > previousPeriodEnd.Value &&
|
||||
stripeSubscription.Status == "active")
|
||||
{
|
||||
user.ChecksUsedThisMonth = 0;
|
||||
_logger.LogInformation("Reset monthly usage for user {UserId} - new billing period", user.Id);
|
||||
}
|
||||
|
||||
// Handle plan changes from Stripe portal
|
||||
var planString = stripeSubscription.Metadata.GetValueOrDefault("plan");
|
||||
if (!string.IsNullOrEmpty(planString) && Enum.TryParse<UserPlan>(planString, out var plan))
|
||||
{
|
||||
user.Plan = plan;
|
||||
}
|
||||
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation(
|
||||
"Subscription updated for user {UserId}: status {Status}, period end {PeriodEnd}",
|
||||
user.Id, stripeSubscription.Status, newPeriodEnd);
|
||||
}
|
||||
|
||||
private async Task HandleSubscriptionDeleted(Event stripeEvent)
|
||||
{
|
||||
var stripeSubscription = stripeEvent.Data.Object as Stripe.Subscription;
|
||||
if (stripeSubscription == null)
|
||||
{
|
||||
_logger.LogWarning("Could not parse subscription from event");
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await _dbContext.Users
|
||||
.FirstOrDefaultAsync(u => u.StripeSubscriptionId == stripeSubscription.Id);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogDebug("No user found for deleted subscription: {SubscriptionId}", stripeSubscription.Id);
|
||||
return;
|
||||
}
|
||||
|
||||
user.Plan = UserPlan.Free;
|
||||
user.StripeSubscriptionId = null;
|
||||
user.SubscriptionStatus = null;
|
||||
user.CurrentPeriodEnd = null;
|
||||
user.ChecksUsedThisMonth = 0;
|
||||
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation(
|
||||
"User {UserId} downgraded to Free plan - subscription {SubscriptionId} deleted",
|
||||
user.Id, stripeSubscription.Id);
|
||||
}
|
||||
|
||||
private async Task HandlePaymentFailed(Event stripeEvent)
|
||||
{
|
||||
var invoice = stripeEvent.Data.Object as Invoice;
|
||||
if (invoice == null)
|
||||
{
|
||||
_logger.LogWarning("Could not parse invoice from event");
|
||||
return;
|
||||
}
|
||||
|
||||
var user = await _dbContext.Users
|
||||
.FirstOrDefaultAsync(u => u.StripeCustomerId == invoice.CustomerId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogDebug("No user found for customer: {CustomerId}", invoice.CustomerId);
|
||||
return;
|
||||
}
|
||||
|
||||
user.SubscriptionStatus = "past_due";
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogWarning(
|
||||
"Payment failed for user {UserId}, invoice {InvoiceId}. Subscription marked as past_due.",
|
||||
user.Id, invoice.Id);
|
||||
}
|
||||
}
|
||||
133
src/RealCV.Infrastructure/Services/SubscriptionService.cs
Normal file
133
src/RealCV.Infrastructure/Services/SubscriptionService.cs
Normal file
@@ -0,0 +1,133 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using RealCV.Application.DTOs;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Domain.Constants;
|
||||
using RealCV.Domain.Enums;
|
||||
using RealCV.Infrastructure.Data;
|
||||
|
||||
namespace RealCV.Infrastructure.Services;
|
||||
|
||||
public sealed class SubscriptionService : ISubscriptionService
|
||||
{
|
||||
private readonly ApplicationDbContext _dbContext;
|
||||
private readonly ILogger<SubscriptionService> _logger;
|
||||
|
||||
public SubscriptionService(
|
||||
ApplicationDbContext dbContext,
|
||||
ILogger<SubscriptionService> logger)
|
||||
{
|
||||
_dbContext = dbContext;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task<bool> CanPerformCheckAsync(Guid userId)
|
||||
{
|
||||
var user = await _dbContext.Users
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(u => u.Id == userId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("User not found for quota check: {UserId}", userId);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Enterprise users have unlimited checks
|
||||
if (PlanLimits.IsUnlimited(user.Plan))
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
// Check if subscription is in good standing for paid plans
|
||||
if (user.Plan != UserPlan.Free)
|
||||
{
|
||||
if (user.SubscriptionStatus == "canceled" || user.SubscriptionStatus == "unpaid")
|
||||
{
|
||||
_logger.LogWarning(
|
||||
"User {UserId} subscription status is {Status} - denying check",
|
||||
userId, user.SubscriptionStatus);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var limit = PlanLimits.GetMonthlyLimit(user.Plan);
|
||||
var canPerform = user.ChecksUsedThisMonth < limit;
|
||||
|
||||
if (!canPerform)
|
||||
{
|
||||
_logger.LogInformation(
|
||||
"User {UserId} has reached quota: {Used}/{Limit} checks",
|
||||
userId, user.ChecksUsedThisMonth, limit);
|
||||
}
|
||||
|
||||
return canPerform;
|
||||
}
|
||||
|
||||
public async Task IncrementUsageAsync(Guid userId)
|
||||
{
|
||||
var user = await _dbContext.Users.FindAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("User not found for usage increment: {UserId}", userId);
|
||||
return;
|
||||
}
|
||||
|
||||
user.ChecksUsedThisMonth++;
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogDebug(
|
||||
"Incremented usage for user {UserId}: {Count} checks this month",
|
||||
userId, user.ChecksUsedThisMonth);
|
||||
}
|
||||
|
||||
public async Task ResetUsageAsync(Guid userId)
|
||||
{
|
||||
var user = await _dbContext.Users.FindAsync(userId);
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("User not found for usage reset: {UserId}", userId);
|
||||
return;
|
||||
}
|
||||
|
||||
user.ChecksUsedThisMonth = 0;
|
||||
await _dbContext.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("Reset monthly usage for user {UserId}", userId);
|
||||
}
|
||||
|
||||
public async Task<SubscriptionInfoDto> GetSubscriptionInfoAsync(Guid userId)
|
||||
{
|
||||
var user = await _dbContext.Users
|
||||
.AsNoTracking()
|
||||
.FirstOrDefaultAsync(u => u.Id == userId);
|
||||
|
||||
if (user == null)
|
||||
{
|
||||
_logger.LogWarning("User not found for subscription info: {UserId}", userId);
|
||||
return new SubscriptionInfoDto
|
||||
{
|
||||
Plan = UserPlan.Free,
|
||||
MonthlyLimit = PlanLimits.GetMonthlyLimit(UserPlan.Free),
|
||||
DisplayPrice = PlanLimits.GetDisplayPrice(UserPlan.Free)
|
||||
};
|
||||
}
|
||||
|
||||
var limit = PlanLimits.GetMonthlyLimit(user.Plan);
|
||||
var isUnlimited = PlanLimits.IsUnlimited(user.Plan);
|
||||
|
||||
return new SubscriptionInfoDto
|
||||
{
|
||||
Plan = user.Plan,
|
||||
ChecksUsedThisMonth = user.ChecksUsedThisMonth,
|
||||
MonthlyLimit = limit,
|
||||
ChecksRemaining = isUnlimited ? int.MaxValue : Math.Max(0, limit - user.ChecksUsedThisMonth),
|
||||
IsUnlimited = isUnlimited,
|
||||
SubscriptionStatus = user.SubscriptionStatus,
|
||||
CurrentPeriodEnd = user.CurrentPeriodEnd,
|
||||
HasActiveSubscription = !string.IsNullOrEmpty(user.StripeSubscriptionId) &&
|
||||
(user.SubscriptionStatus == "active" || user.SubscriptionStatus == "past_due"),
|
||||
DisplayPrice = PlanLimits.GetDisplayPrice(user.Plan)
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
using Microsoft.Extensions.Logging;
|
||||
using TrueCV.Application.Interfaces;
|
||||
using TrueCV.Application.Models;
|
||||
using RealCV.Application.Interfaces;
|
||||
using RealCV.Application.Models;
|
||||
|
||||
namespace TrueCV.Infrastructure.Services;
|
||||
namespace RealCV.Infrastructure.Services;
|
||||
|
||||
public sealed class TimelineAnalyserService : ITimelineAnalyserService
|
||||
{
|
||||
@@ -1,8 +1,8 @@
|
||||
using System.Security.Claims;
|
||||
using Microsoft.AspNetCore.Components.Authorization;
|
||||
using TrueCV.Application.Interfaces;
|
||||
using RealCV.Application.Interfaces;
|
||||
|
||||
namespace TrueCV.Infrastructure.Services;
|
||||
namespace RealCV.Infrastructure.Services;
|
||||
|
||||
public sealed class UserContextService : IUserContextService
|
||||
{
|
||||
@@ -7,7 +7,7 @@
|
||||
<base href="/" />
|
||||
<link rel="stylesheet" href="bootstrap/bootstrap.min.css" />
|
||||
<link rel="stylesheet" href="app.css" />
|
||||
<link rel="stylesheet" href="TrueCV.Web.styles.css" />
|
||||
<link rel="stylesheet" href="RealCV.Web.styles.css" />
|
||||
<link rel="icon" type="image/png" href="favicon.png" />
|
||||
<HeadOutlet @rendermode="InteractiveServer" />
|
||||
</head>
|
||||
@@ -1,10 +1,10 @@
|
||||
@inherits LayoutComponentBase
|
||||
|
||||
<div class="d-flex flex-column min-vh-100">
|
||||
<nav class="navbar navbar-expand-lg navbar-light shadow-sm" style="background-color: var(--truecv-bg-surface);">
|
||||
<nav class="navbar navbar-expand-lg navbar-light shadow-sm" style="background-color: var(--realcv-bg-surface);">
|
||||
<div class="container">
|
||||
<a class="navbar-brand fw-bold" href="/">
|
||||
<img src="images/TrueCV_Logo.png" alt="TrueCV" style="height: 95px;" />
|
||||
<img src="images/RealCV_Logo_Transparent.png" alt="RealCV" style="height: 95px;" />
|
||||
</a>
|
||||
|
||||
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav" aria-controls="navbarNav" aria-expanded="false" aria-label="Toggle navigation">
|
||||
@@ -77,9 +77,17 @@
|
||||
@Body
|
||||
</main>
|
||||
|
||||
<footer class="text-light py-4 mt-auto" style="background-color: var(--truecv-footer-bg);">
|
||||
<div class="container text-center">
|
||||
<p class="mb-0">© @DateTime.Now.Year TrueCV. All rights reserved.</p>
|
||||
<footer class="text-light py-4 mt-auto" style="background-color: var(--realcv-footer-bg);">
|
||||
<div class="container">
|
||||
<div class="row align-items-center">
|
||||
<div class="col-md-6 text-center text-md-start mb-2 mb-md-0">
|
||||
<p class="mb-0">© @DateTime.Now.Year RealCV. All rights reserved.</p>
|
||||
</div>
|
||||
<div class="col-md-6 text-center text-md-end">
|
||||
<a href="/privacy" class="text-light text-decoration-none me-3">Privacy Policy</a>
|
||||
<span class="text-muted small">GDPR Compliant</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
220
src/RealCV.Web/Components/Pages/Account/Billing.razor
Normal file
220
src/RealCV.Web/Components/Pages/Account/Billing.razor
Normal file
@@ -0,0 +1,220 @@
|
||||
@page "/account/billing"
|
||||
@attribute [Authorize]
|
||||
@rendermode InteractiveServer
|
||||
|
||||
@inject ISubscriptionService SubscriptionService
|
||||
@inject AuthenticationStateProvider AuthenticationStateProvider
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<PageTitle>Billing - RealCV</PageTitle>
|
||||
|
||||
<div class="container py-5">
|
||||
<div class="row justify-content-center">
|
||||
<div class="col-lg-8">
|
||||
<div class="mb-4">
|
||||
<h1 class="fw-bold mb-1">Billing & Subscription</h1>
|
||||
<p class="text-muted">Manage your subscription and view usage</p>
|
||||
</div>
|
||||
|
||||
@if (!string.IsNullOrEmpty(_errorMessage))
|
||||
{
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
@_errorMessage
|
||||
<button type="button" class="btn-close" @onclick="() => _errorMessage = null"></button>
|
||||
</div>
|
||||
}
|
||||
|
||||
@if (_isLoading)
|
||||
{
|
||||
<div class="text-center py-5">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else if (_subscription != null)
|
||||
{
|
||||
<!-- Current Plan Card -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
<div class="d-flex justify-content-between align-items-start mb-4">
|
||||
<div>
|
||||
<h5 class="fw-bold mb-1">Current Plan</h5>
|
||||
<p class="text-muted small mb-0">Your active subscription details</p>
|
||||
</div>
|
||||
<span class="badge bg-primary-subtle text-primary px-3 py-2 fs-6">
|
||||
@_subscription.Plan
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="row g-4">
|
||||
<div class="col-md-6">
|
||||
<div class="p-3 rounded" style="background: var(--realcv-bg-muted);">
|
||||
<div class="small text-muted mb-1">Price</div>
|
||||
<div class="fw-bold fs-4">@_subscription.DisplayPrice</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<div class="p-3 rounded" style="background: var(--realcv-bg-muted);">
|
||||
<div class="small text-muted mb-1">Status</div>
|
||||
<div class="fw-bold fs-4">
|
||||
@if (_subscription.HasActiveSubscription)
|
||||
{
|
||||
<span class="text-success">Active</span>
|
||||
}
|
||||
else if (_subscription.Plan == RealCV.Domain.Enums.UserPlan.Free)
|
||||
{
|
||||
<span class="text-muted">Free Tier</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-warning">@(_subscription.SubscriptionStatus ?? "Inactive")</span>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@if (_subscription.CurrentPeriodEnd.HasValue)
|
||||
{
|
||||
<div class="mt-3 small text-muted">
|
||||
Next billing date: <strong>@_subscription.CurrentPeriodEnd.Value.ToString("dd MMMM yyyy")</strong>
|
||||
</div>
|
||||
}
|
||||
|
||||
<div class="d-flex gap-2 mt-4">
|
||||
<a href="/pricing" class="btn btn-primary">
|
||||
@if (_subscription.Plan == RealCV.Domain.Enums.UserPlan.Free)
|
||||
{
|
||||
<span>Upgrade Plan</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>Change Plan</span>
|
||||
}
|
||||
</a>
|
||||
@if (_subscription.HasActiveSubscription)
|
||||
{
|
||||
<form action="/api/billing/portal" method="post">
|
||||
<button type="submit" class="btn btn-outline-secondary">
|
||||
Manage Subscription
|
||||
</button>
|
||||
</form>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Usage Card -->
|
||||
<div class="card border-0 shadow-sm mb-4">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="fw-bold mb-4">Usage This Month</h5>
|
||||
|
||||
<div class="mb-3">
|
||||
<div class="d-flex justify-content-between align-items-center mb-2">
|
||||
<span class="text-muted">CV Checks</span>
|
||||
<span class="fw-semibold">
|
||||
@if (_subscription.IsUnlimited)
|
||||
{
|
||||
<span>@_subscription.ChecksUsedThisMonth used (Unlimited)</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@_subscription.ChecksUsedThisMonth / @_subscription.MonthlyLimit</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
@if (!_subscription.IsUnlimited)
|
||||
{
|
||||
var percentage = _subscription.MonthlyLimit > 0
|
||||
? Math.Min(100, (_subscription.ChecksUsedThisMonth * 100) / _subscription.MonthlyLimit)
|
||||
: 0;
|
||||
var progressClass = percentage >= 90 ? "bg-danger" : percentage >= 75 ? "bg-warning" : "bg-primary";
|
||||
|
||||
<div class="progress" style="height: 10px;">
|
||||
<div class="progress-bar @progressClass" role="progressbar" style="width: @percentage%"></div>
|
||||
</div>
|
||||
|
||||
@if (_subscription.ChecksRemaining <= 0)
|
||||
{
|
||||
<div class="alert alert-warning mt-3 mb-0 py-2">
|
||||
<small>
|
||||
You've used all your checks this month.
|
||||
<a href="/pricing" class="alert-link">Upgrade your plan</a> for more.
|
||||
</small>
|
||||
</div>
|
||||
}
|
||||
else if (_subscription.ChecksRemaining <= 3 && _subscription.Plan != RealCV.Domain.Enums.UserPlan.Free)
|
||||
{
|
||||
<div class="alert alert-info mt-3 mb-0 py-2">
|
||||
<small>
|
||||
You have @_subscription.ChecksRemaining checks remaining this month.
|
||||
</small>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Manage Billing Card (for paid users) -->
|
||||
@if (_subscription.HasActiveSubscription)
|
||||
{
|
||||
<div class="card border-0 shadow-sm">
|
||||
<div class="card-body p-4">
|
||||
<h5 class="fw-bold mb-3">Billing Management</h5>
|
||||
<p class="text-muted mb-4">
|
||||
Use the Stripe Customer Portal to update your payment method, view invoices, or cancel your subscription.
|
||||
</p>
|
||||
<form action="/api/billing/portal" method="post">
|
||||
<button type="submit" class="btn btn-outline-primary">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="me-2" viewBox="0 0 16 16">
|
||||
<path d="M0 4a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2v8a2 2 0 0 1-2 2H2a2 2 0 0 1-2-2V4zm2-1a1 1 0 0 0-1 1v1h14V4a1 1 0 0 0-1-1H2zm13 4H1v5a1 1 0 0 0 1 1h12a1 1 0 0 0 1-1V7z"/>
|
||||
<path d="M2 10a1 1 0 0 1 1-1h1a1 1 0 0 1 1 1v1a1 1 0 0 1-1 1H3a1 1 0 0 1-1-1v-1z"/>
|
||||
</svg>
|
||||
Open Billing Portal
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
private SubscriptionInfoDto? _subscription;
|
||||
private bool _isLoading = true;
|
||||
private string? _errorMessage;
|
||||
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
var uri = NavigationManager.ToAbsoluteUri(NavigationManager.Uri);
|
||||
if (Microsoft.AspNetCore.WebUtilities.QueryHelpers.ParseQuery(uri.Query).TryGetValue("error", out var error))
|
||||
{
|
||||
_errorMessage = error == "portal_failed"
|
||||
? "Unable to open billing portal. Please try again."
|
||||
: error.ToString();
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
|
||||
var userIdClaim = authState.User.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
|
||||
if (!string.IsNullOrEmpty(userIdClaim) && Guid.TryParse(userIdClaim, out var userId))
|
||||
{
|
||||
_subscription = await SubscriptionService.GetSubscriptionInfoAsync(userId);
|
||||
}
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
_errorMessage = "Unable to load subscription information.";
|
||||
}
|
||||
finally
|
||||
{
|
||||
_isLoading = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user