Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
399 changes: 383 additions & 16 deletions README.md

Large diffs are not rendered by default.

219 changes: 219 additions & 0 deletions app/README.md

Large diffs are not rendered by default.

320 changes: 277 additions & 43 deletions backend/README.md

Large diffs are not rendered by default.

399 changes: 399 additions & 0 deletions docs/AUDIT_LOG.md

Large diffs are not rendered by default.

907 changes: 907 additions & 0 deletions docs/SoundScore_V1_Architecture_Report.md

Large diffs are not rendered by default.

Binary file added docs/SoundScore_V1_Architecture_Report.pdf
Binary file not shown.
369 changes: 369 additions & 0 deletions docs/SoundScore_V1_Mobile_Architecture_Report.md

Large diffs are not rendered by default.

Binary file not shown.
286 changes: 286 additions & 0 deletions ios/README.md

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -288,7 +288,7 @@ struct CadenceBatchRatingCard: View {
private func applyAllWithAnimation() {
for (index, _) in ratings.enumerated() {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(index) * 0.3) {
withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
_ = withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
appliedIndices.insert(index)
}
UIImpactFeedbackGenerator(style: .light).impactOccurred()
Expand Down
8 changes: 8 additions & 0 deletions ios/SoundScore/SoundScore/Services/APIClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,14 @@ final class APIClient {
try await raw(method: "GET", path: path, authenticated: authenticated)
}

func postRaw(
_ path: String,
body: Data? = nil,
authenticated: Bool = true
) async throws -> Data {
try await raw(method: "POST", path: path, body: body, authenticated: authenticated)
}

// MARK: - Core

private func raw(
Expand Down
4 changes: 2 additions & 2 deletions ios/SoundScore/SoundScore/Services/AuthManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,8 @@ class AuthManager: ObservableObject {
#if DEBUG
/// Attempts signup with dev credentials, falls back to login if account already exists.
func devAutoSignup() async throws {
let email = "dev@soundscore.test"
let password = "devpass1234"
let email = "phase1b@local.soundscore.app"
let password = "soundscore-dev-pass"
let handle = "madhav"
do {
try await signup(email: email, password: password, handle: handle)
Expand Down
2 changes: 1 addition & 1 deletion ios/SoundScore/SoundScore/Services/SoundScoreAPI.swift
Original file line number Diff line number Diff line change
Expand Up @@ -289,7 +289,7 @@ struct SoundScoreAPI {
// MARK: Trust

func exportData() async throws -> Data {
try await client.getRaw("/v1/account/export")
try await client.postRaw("/v1/account/export")
}

func deleteAccount() async throws {
Expand Down
12 changes: 12 additions & 0 deletions ios/SoundScore/SoundScore/Services/SoundScoreRepository.swift
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,13 @@ class SoundScoreRepository: ObservableObject {
self.latestRecap = SeedData.initialRecap
self.syncMessage = nil
Task { await enrichAlbumsWithArtwork() }
#if DEBUG
Task {
try? await AuthManager.shared.devAutoSignup()
await refresh()
await syncOutbox()
}
#endif
}

// MARK: - Spotify Artwork Enrichment
Expand Down Expand Up @@ -60,6 +67,11 @@ class SoundScoreRepository: ObservableObject {
// MARK: - Refresh from API

func refresh() async {
#if DEBUG
if !AuthManager.shared.isAuthenticated {
try? await AuthManager.shared.devAutoSignup()
}
#endif
guard AuthManager.shared.isAuthenticated else { return }

await MainActor.run {
Expand Down
69 changes: 69 additions & 0 deletions packages/contracts/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# @soundscore/contracts

> Shared TypeScript type definitions for the SoundScore API

## Purpose

Single source of truth for API request/response shapes, event schemas, and endpoint contracts shared between backend and future web client. All schemas are defined with [Zod](https://zod.dev/) and export both runtime validators and inferred TypeScript types.

## Files

| File | Description | Key Exports |
|------|-------------|-------------|
| `common.ts` | Pagination, error envelope, and idempotency primitives | `CursorPageSchema`, `ErrorEnvelopeSchema`, `IdempotencyKeyHeaderSchema` |
| `models.ts` | Core domain models (albums, ratings, reviews, lists, users, notifications) | `AlbumSchema`, `RatingSchema`, `TrackSchema`, `TrackRatingSchema`, `ReviewSchema`, `UserProfileSchema`, `ListSchema`, `WeeklyRecapSchema`, `NotificationPreferenceSchema`, `DeviceTokenSchema` |
| `endpoints.ts` | Request/response schemas for API routes (auth, ratings, reviews, lists, social, notifications) | `SignUpRequestSchema`, `LoginRequestSchema`, `RefreshRequestSchema`, `AuthResponseSchema`, `CreateRatingRequestSchema`, `CreateTrackRatingRequestSchema`, `CreateReviewRequestSchema`, `UpdateReviewRequestSchema`, `CreateListRequestSchema`, `AddListItemRequestSchema`, `ReactActivityRequestSchema`, `CommentActivityRequestSchema`, `UpsertNotificationPreferenceSchema`, `RegisterDeviceTokenRequestSchema` |
| `events.ts` | Activity feed and listening history event shapes | `ActivityTypeSchema`, `ActivityEventSchema`, `ListeningEventSchema` |
| `provider.ts` | OAuth provider connection, status, and disconnection contracts | `ProviderName`, `ProviderErrorCode`, `ConnectProviderRequestSchema`, `OAuthCallbackRequestSchema`, `ProviderConnectionSchema`, `ProviderStatusResponseSchema`, `DisconnectProviderRequestSchema` |
| `mapping.ts` | Canonical entity resolution and cross-provider ID mapping | `CanonicalEntityType`, `CanonicalArtistSchema`, `CanonicalAlbumSchema`, `MappingStatus`, `MappingProvenance`, `ProviderMappingSchema`, `MappingLookupRequestSchema`, `MappingLookupResponseSchema`, `ResolveMappingRequestSchema` |
| `compliance.ts` | Provider attribution, branding, and data-retention policies | `AttributionPlacement`, `AttributionRequirementSchema`, `ComplianceViolationSchema`, `ComplianceCheckResponseSchema`, `DataRetentionPolicySchema` |
| `sync.ts` | Sync job lifecycle, cursor bookmarks, and ingested listening events | `SyncType`, `SyncStatus`, `SyncTriggerRequestSchema`, `SyncJobSchema`, `SyncStatusResponseSchema`, `SyncCursorSchema`, `SyncListeningEventSchema`, `CancelSyncRequestSchema` |

## Type Aliases

Every Zod schema also exports a corresponding TypeScript type via `z.infer`. For example:

- `Album`, `Rating`, `Track`, `TrackRating`, `Review`, `UserProfile`, `SoundScoreList`, `WeeklyRecap`, `NotificationPreference`, `DeviceToken`
- `ActivityEvent`, `ListeningEvent`
- `ProviderConnection`, `ProviderStatusResponse`, `ConnectProviderRequest`, `OAuthCallbackRequest`, `DisconnectProviderRequest`
- `CanonicalArtist`, `CanonicalAlbum`, `ProviderMapping`, `MappingLookupRequest`, `MappingLookupResponse`, `ResolveMappingRequest`
- `AttributionRequirement`, `ComplianceViolation`, `ComplianceCheckResponse`, `DataRetentionPolicy`
- `SyncTriggerRequest`, `SyncJob`, `SyncStatusResponse`, `SyncCursor`, `SyncListeningEvent`, `CancelSyncRequest`
- `ErrorEnvelope`

## Usage

```ts
import {
AlbumSchema,
UserProfileSchema,
CreateRatingRequestSchema,
ErrorEnvelopeSchema,
type Album,
type UserProfile,
} from "@soundscore/contracts";

// Runtime validation
const album = AlbumSchema.parse(rawJson);

// Type-safe access
console.log(album.title, album.avgRating);
```

## Build

```bash
npm run build --workspace @soundscore/contracts
npm run typecheck --workspace @soundscore/contracts
```

## Dependencies

- **zod** `^3.24.1` -- runtime schema validation and TypeScript type inference
- **typescript** `^5.7.2` (dev) -- compilation and type checking

## Coverage Gap

Only 16 of 36 backend routes have typed contract schemas. Phase 2 routes (search, discovery, admin, moderation) lack contracts entirely. Expanding coverage is tracked as future work.

Last audited: 2026-03-19
Loading