260 lines
8.7 KiB
Plaintext
260 lines
8.7 KiB
Plaintext
---
|
|
description: Endorser-ch API pagination conventions and database migration documentation
|
|
globs: repos/endorser-ch/src/**
|
|
alwaysApply: true
|
|
---
|
|
|
|
# Endorser-ch Development Conventions
|
|
|
|
# Pagination Conventions
|
|
|
|
### Standard Pagination Parameters
|
|
|
|
### Required Pattern
|
|
- **`beforeId`**: JWT ID to end before (exclusive) - primary pagination parameter
|
|
- **`afterId`**: JWT ID to start after (exclusive) - used for feed-style results where newer items are needed
|
|
|
|
### Parameter Usage
|
|
- **Routes**: Accept `beforeId` and `afterId` as query parameters
|
|
- **Database methods**: Pass these parameters through to database layer
|
|
- **Never use**: `limit`, `offset`, `page`, `skip`, or similar traditional pagination
|
|
|
|
### Limit
|
|
- The limit is managed by the DB service, currently always set to DEFAULT_LIMIT
|
|
|
|
## API Route Implementation
|
|
|
|
### Query Parameter Handling
|
|
```javascript
|
|
// ✅ CORRECT - Use beforeId/afterId pattern
|
|
const beforeId = req.query.beforeId
|
|
const afterId = req.query.afterId
|
|
dbService.methodByParamsPaged(params, afterId, beforeId)
|
|
|
|
// ❌ WRONG - Never use limit/offset
|
|
const limit = parseInt(req.query.limit) || 50 // DON'T DO THIS
|
|
const offset = parseInt(req.query.offset) || 0 // DON'T DO THIS
|
|
```
|
|
|
|
### JSDoc Documentation Pattern
|
|
```javascript
|
|
/**
|
|
* @param {string} beforeId.query.optional - the JWT ID of the entry before which to look (exclusive); by default, the last one is included
|
|
* @param {string} afterId.query.optional - the JWT ID of the entry after which to look (exclusive); by default, the first one is included
|
|
*/
|
|
```
|
|
|
|
## Database Service Implementation
|
|
|
|
### Method Signature Pattern
|
|
```javascript
|
|
// ✅ CORRECT - Standard pagination signature
|
|
methodByParamsPaged(params, afterIdInput, beforeIdInput) {
|
|
// Implementation using JWT ID comparisons
|
|
}
|
|
|
|
// ❌ WRONG - Never use limit/offset in method signatures
|
|
methodByParamsPaged(params, limit, offset) { // DON'T DO THIS
|
|
```
|
|
|
|
### SQL Implementation
|
|
```javascript
|
|
// ✅ CORRECT - Use JWT ID comparisons
|
|
let sql = "SELECT * FROM table WHERE conditions"
|
|
if (afterIdInput) {
|
|
params.push(afterIdInput)
|
|
sql += " AND jwtId > ?"
|
|
}
|
|
if (beforeIdInput) {
|
|
params.push(beforeIdInput)
|
|
sql += " AND jwtId < ?"
|
|
}
|
|
sql += " ORDER BY jwtId DESC LIMIT " + DEFAULT_LIMIT
|
|
|
|
// ❌ WRONG - Never use OFFSET
|
|
sql += " LIMIT ? OFFSET ?" // DON'T DO THIS
|
|
```
|
|
|
|
### Response Format
|
|
```javascript
|
|
// ✅ CORRECT - Standard response format
|
|
const result = {
|
|
data: processedResults,
|
|
hitLimit: results.length === DEFAULT_LIMIT
|
|
}
|
|
|
|
// ❌ WRONG - Don't include limit/offset metadata
|
|
const result = {
|
|
data: results,
|
|
limit: 50, // DON'T DO THIS
|
|
offset: 100 // DON'T DO THIS
|
|
}
|
|
```
|
|
|
|
## Consistent Patterns Across Codebase
|
|
|
|
### Why This Pattern?
|
|
1. **Consistency**: JWT IDs provide stable, chronological ordering
|
|
2. **Performance**: Index-based lookups are faster than OFFSET
|
|
3. **Reliability**: Results don't shift when new records are added
|
|
|
|
## Implementation Checklist
|
|
|
|
When creating new paginated endpoints:
|
|
|
|
- [ ] Use `beforeId`/`afterId` query parameters in routes
|
|
- [ ] Pass these parameters to database methods as `afterIdInput`/`beforeIdInput`
|
|
- [ ] Use JWT ID comparisons (`jwtId > ?`, `jwtId < ?`) in SQL
|
|
- [ ] Return `{ data: [], hitLimit: boolean }` response format
|
|
- [ ] Document parameters with standard JSDoc format
|
|
- [ ] Never use `limit`, `offset`, `page`, or similar parameters
|
|
|
|
## Error Prevention
|
|
|
|
**Red Flags** - These patterns should be avoided:
|
|
- Any mention of `limit` or `offset` in parameter names
|
|
- `LIMIT ? OFFSET ?` in SQL queries
|
|
- `parseInt(req.query.limit)` in route handlers
|
|
- Response objects with `page`, `totalPages`, `totalCount` fields
|
|
|
|
**Green Flags** - These patterns should be used:
|
|
- `beforeId`/`afterId` parameter naming
|
|
- `jwtId > ?` and `jwtId < ?` SQL conditions
|
|
- `hitLimit` boolean in responses
|
|
- `DEFAULT_LIMIT` constant for consistent limits
|
|
|
|
# Database Migration Documentation
|
|
|
|
## CRITICAL: Always Update sql/README.md
|
|
|
|
**Every database migration MUST be documented in `sql/README.md`** because migration files cannot be edited after flyway has run.
|
|
|
|
## Migration Documentation Process
|
|
|
|
When creating a new migration file (e.g., `V18__new_feature.sqlite3`):
|
|
|
|
1. **Create the migration file** with proper SQL schema changes
|
|
2. **Immediately update `sql/README.md`** with the new schema
|
|
3. **Add explanatory comments** for complex fields or relationships
|
|
4. **Update indexes** documentation if new indexes are created
|
|
|
|
## Schema Update Checklist
|
|
|
|
When creating a migration:
|
|
|
|
- [ ] Create migration file with proper version number (VXX__description.sqlite3)
|
|
- [ ] Add comprehensive SQL comments in migration file
|
|
- [ ] Update sql/README.md with complete table schema
|
|
- [ ] Document all new indexes in README.md
|
|
- [ ] Explain field purposes and constraints
|
|
- [ ] Verify README.md reflects current schema state
|
|
- [ ] Test migration on development database
|
|
|
|
## Why This Documentation Matters
|
|
|
|
1. **Flyway Immutability**: Migration files cannot be edited after deployment
|
|
2. **Schema Clarity**: README.md provides the definitive current schema
|
|
3. **Developer Onboarding**: New developers need clear schema documentation
|
|
4. **Maintenance**: Future changes require understanding current structure
|
|
|
|
# Data Privacy and DID Hiding
|
|
|
|
## hideDidsAndAddLinksToNetwork Function
|
|
|
|
The `hideDidsAndAddLinksToNetwork` function is a critical security component that ensures searches for DIDs (Decentralized Identifiers) only show results that are visible to users who have proper permissions to see them.
|
|
|
|
### Purpose
|
|
|
|
This function processes API response data to:
|
|
1. **Hide DIDs** that the requesting user doesn't have permission to see
|
|
2. **Add network links** showing which DIDs in the user's network can see hidden data
|
|
3. **Include public URLs** for DIDs that have published public profiles
|
|
4. **Validate search terms** to prevent data leakage through search queries
|
|
|
|
### Function Signatures
|
|
|
|
```javascript
|
|
// Primary function
|
|
async function hideDidsAndAddLinksToNetwork(requesterDid, inputData, searchTerms)
|
|
|
|
// Wrapper for responses with a "data" key
|
|
async function hideDidsAndAddLinksToNetworkInDataKey(requesterDid, input, searchTerms)
|
|
```
|
|
|
|
### Critical searchTerms Parameter
|
|
|
|
**SECURITY REQUIREMENT**: The `searchTerms` parameter MUST contain any user-provided query data that might include DIDs or parts of DIDs.
|
|
|
|
#### Why searchTerms is Required
|
|
|
|
The function validates that after hiding DIDs, all search terms are still visible in the result. This prevents users from:
|
|
- Searching for a DID they can't see and getting results that reveal its existence
|
|
- Using partial DID strings to probe for hidden data
|
|
- Discovering private information through search inference
|
|
|
|
#### Implementation Pattern
|
|
|
|
```javascript
|
|
// ✅ CORRECT - Include all user query parameters that might contain DIDs
|
|
const searchTermMayBeDIDs = [
|
|
req.query.claimContents,
|
|
req.query.issuer,
|
|
req.query.subject,
|
|
req.query.handleId
|
|
]
|
|
dbService.someMethod(params)
|
|
.then(results => hideDidsAndAddLinksToNetwork(
|
|
res.locals.authTokenIssuer,
|
|
results,
|
|
searchTermMayBeDIDs
|
|
))
|
|
|
|
// ❌ WRONG - Empty array when user provided search terms
|
|
.then(results => hideDidsAndAddLinksToNetwork(
|
|
res.locals.authTokenIssuer,
|
|
results,
|
|
[] // DON'T DO THIS if user provided searchable parameters
|
|
))
|
|
```
|
|
|
|
### Usage Guidelines
|
|
|
|
#### When to Use Empty searchTerms Array
|
|
|
|
Use `[]` only when:
|
|
- No user input was used to filter the results
|
|
- The endpoint doesn't accept search parameters
|
|
- You're certain no DIDs could be in the query parameters
|
|
|
|
#### When to Include Search Terms
|
|
|
|
Always include search terms when:
|
|
- User provided `claimContents`, `issuer`, `subject`, `handleId` parameters
|
|
- Any text search functionality is involved
|
|
- User input could contain DIDs or partial DIDs
|
|
- The query filters data based on user-provided strings
|
|
|
|
### Security Checklist
|
|
|
|
When implementing endpoints that use `hideDidsAndAddLinksToNetwork`:
|
|
|
|
- [ ] Identify all user-provided query parameters
|
|
- [ ] Include any parameter that could contain DIDs in `searchTerms`
|
|
- [ ] Include any text search parameters in `searchTerms`
|
|
- [ ] Use empty array `[]` only when no user input affects the query
|
|
- [ ] Test that search results don't leak hidden DID information
|
|
- [ ] Verify that partial DID searches are properly filtered
|
|
|
|
### Error Prevention
|
|
|
|
**Red Flags** - These patterns indicate potential security issues:
|
|
- Using `[]` for searchTerms when user provided search parameters
|
|
- Not including text search fields in searchTerms
|
|
- Forgetting to include DID-related query parameters
|
|
- Bypassing the function for "simple" endpoints that still return DID data
|
|
|
|
**Green Flags** - These patterns indicate proper usage:
|
|
- Including all user query parameters that might contain DIDs
|
|
- Using `[]` only for endpoints with no user search input
|
|
- Consistent application across all endpoints returning DID data
|
|
- Proper testing of search result filtering |