Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
159 commits
Select commit Hold shift + click to select a range
ebd9994
chore: bump sl
jimmykane Jan 23, 2026
12654f6
chore: fix injection context
jimmykane Jan 23, 2026
c79f8c7
chore: app component fixes
jimmykane Jan 23, 2026
217cc6c
chore: auth service fixes
jimmykane Jan 23, 2026
61c5a57
chore: analytics settings
jimmykane Jan 23, 2026
2531e8b
fix: race condition
jimmykane Jan 23, 2026
1979537
chore: lower firebase
jimmykane Jan 23, 2026
593fd00
chore: add last import ranges for history imports
jimmykane Jan 23, 2026
41d4d28
chore: bump sl
jimmykane Jan 23, 2026
4c7cd4a
feature: clusters based on prevailing activity colors
jimmykane Jan 26, 2026
ae356ce
chore: fix logo
jimmykane Jan 26, 2026
3368d93
fix: issue with xAxis
jimmykane Jan 26, 2026
bc5e3f0
chore: improve devices
jimmykane Jan 26, 2026
e34edda
chore: improve home
jimmykane Jan 26, 2026
32e4c70
feature: jumps
jimmykane Jan 26, 2026
90b9771
chore: improve home
jimmykane Jan 26, 2026
13f86ed
chore: more home page info
jimmykane Jan 26, 2026
a7328ea
chore: disable services if not logged in
jimmykane Jan 26, 2026
b882a4b
chore: bump sl
jimmykane Jan 26, 2026
9a6b4bb
chore: fix item highlight
jimmykane Jan 26, 2026
ea19e83
chore: remove debugs
jimmykane Jan 26, 2026
907cc9c
chore: sidenav improvements
jimmykane Jan 26, 2026
ed82ef4
chore: remove debug
jimmykane Jan 26, 2026
0bb9ab7
chore: styles
jimmykane Jan 26, 2026
0c5f08a
chore: remove anonymous
jimmykane Jan 26, 2026
62c3982
chore: remove show points
jimmykane Jan 26, 2026
d40103f
chore: enable persistance
jimmykane Jan 26, 2026
86ba5e7
chore: better storage
jimmykane Jan 26, 2026
4115f6b
chore: use fetch streams
jimmykane Jan 26, 2026
82d0729
chore: improve devices
jimmykane Jan 27, 2026
4a1d104
feature: implement mapbox for 3d
jimmykane Jan 27, 2026
5aa6937
chore: remove guest
jimmykane Jan 27, 2026
2d97664
refactor: rename to logged in guard
jimmykane Jan 27, 2026
c1bc720
chore: add test
jimmykane Jan 27, 2026
b2a6158
chore: styles
jimmykane Jan 27, 2026
6e1b11f
chore: styles for google maps
jimmykane Jan 27, 2026
abac7ad
chore: map styles
jimmykane Jan 27, 2026
64933a3
chore: styles
jimmykane Jan 27, 2026
381a531
refactor: use devices service
jimmykane Jan 27, 2026
49a706d
feature: cache fit files
jimmykane Jan 27, 2026
b1b56a0
chore: fix not disposed charts
jimmykane Jan 27, 2026
dd09d6f
chore: remove dead zones
jimmykane Jan 27, 2026
fc024cf
chore: improve map theme change
jimmykane Jan 27, 2026
54398fa
chore: timeout 0
jimmykane Jan 27, 2026
c93c6a6
chore: fix for intensity zones
jimmykane Jan 27, 2026
bb22383
chore: no need for timeout
jimmykane Jan 27, 2026
124acb2
fix: tests
jimmykane Jan 27, 2026
8aac653
chore: do not aggressively apply the theme
jimmykane Jan 27, 2026
6af6135
chore: have no clue for intensity zones
jimmykane Jan 27, 2026
8544de5
fix: for arrows
jimmykane Jan 27, 2026
c5d8038
refactor: use app user setting service for seperation of settings
jimmykane Jan 27, 2026
930b422
fix: label style
jimmykane Jan 27, 2026
418a95d
fix: activity upload ?
jimmykane Jan 27, 2026
c5b26f7
chore: styles
jimmykane Jan 27, 2026
e0c7995
fix: import activities
jimmykane Jan 27, 2026
a361c03
chore: user settings fixes
jimmykane Jan 28, 2026
1d688d8
feature: get files that cannot be uploaded
jimmykane Jan 28, 2026
23a1f93
chore: improve my tracks
jimmykane Jan 28, 2026
aba279f
chore: bump sl
jimmykane Jan 28, 2026
aaf3b71
chore: fix for map component
jimmykane Jan 28, 2026
4ff3234
chore: bump version
jimmykane Jan 28, 2026
552a84b
chore: rules
jimmykane Jan 28, 2026
3bd34c6
refactor: user token ensureFreshToken to new method
jimmykane Jan 28, 2026
566d013
chore: lints and tests fixes
jimmykane Jan 28, 2026
d2f41df
chore: home component fixes
jimmykane Jan 28, 2026
e465710
chore: remove jetbrains
jimmykane Jan 28, 2026
7316cc2
feature: glow for my tracks
jimmykane Jan 28, 2026
21ddbe9
chore: reduce glow
jimmykane Jan 28, 2026
61bc53a
chore: add all option
jimmykane Jan 28, 2026
20b3a76
Merge branch 'main' into develop
jimmykane Jan 28, 2026
31658aa
fix: home title
jimmykane Jan 28, 2026
ea88837
chore: home
jimmykane Jan 28, 2026
2cf48c5
chore: hide button from admin
jimmykane Jan 28, 2026
88a37e9
Merge remote-tracking branch 'origin/develop' into develop
jimmykane Jan 28, 2026
ddc6a12
fix: my tracks double loads
jimmykane Jan 28, 2026
7e7a546
chore: remove snacky
jimmykane Jan 28, 2026
658cc69
fix: 3d initi
jimmykane Jan 28, 2026
e82688c
chore: increase padding
jimmykane Jan 28, 2026
8afc999
refactor: improve tile actions
jimmykane Jan 29, 2026
1b824ad
chore: disable interactions
jimmykane Jan 29, 2026
76655fc
chore: remove export menu
jimmykane Jan 29, 2026
8fb5ecf
chore: remove anotation
jimmykane Jan 29, 2026
5da913d
chore: restore marker
jimmykane Jan 29, 2026
92abb93
chore: fixes
jimmykane Jan 29, 2026
e58b8c2
fix: more chart stuff
jimmykane Jan 29, 2026
9ba06aa
chore: fixes
jimmykane Jan 29, 2026
169f940
chore: fixes
jimmykane Jan 29, 2026
5ab1bae
fix: map pointer
jimmykane Jan 29, 2026
b43cf30
chore: more throttle
jimmykane Jan 29, 2026
c31d2cc
chore: better debug
jimmykane Jan 29, 2026
d647526
chore: add timing
jimmykane Jan 29, 2026
7ecb2a1
fix: tests
jimmykane Jan 29, 2026
3448103
chore: improve activity refresh
jimmykane Jan 29, 2026
8ed5210
chore: more zones preperation
jimmykane Jan 29, 2026
5fa9b21
chore: use track-manager
jimmykane Jan 29, 2026
557b9bf
chore: track improvements
jimmykane Jan 29, 2026
5489269
refactor: my tracks
jimmykane Jan 30, 2026
116bfce
chore: deprecation fix
jimmykane Jan 30, 2026
9f485f2
chore: angular shit
jimmykane Jan 30, 2026
580b05a
chore: better lazy loading
jimmykane Jan 30, 2026
b40bfe9
refactor: mapbox stuff and my tracks stuff
jimmykane Jan 30, 2026
e2aea61
chore: do no force night on sat
jimmykane Jan 30, 2026
6510d8a
chore: bump sl
jimmykane Jan 30, 2026
bfd46bc
fix: seo
jimmykane Jan 30, 2026
e9bf57e
fix: polylines
jimmykane Jan 30, 2026
7a04a2f
chore: use the new icons
jimmykane Jan 30, 2026
3ab3d50
fix: syles for dark
jimmykane Jan 30, 2026
ba2ca81
chore: mapbox and tracks
jimmykane Jan 30, 2026
f15fbfe
chore: icon improvements
jimmykane Jan 30, 2026
fb99acf
chore: fix tests and use signals for tracks
jimmykane Jan 30, 2026
a79c4c2
chore: change icon
jimmykane Jan 30, 2026
3819440
chore: logs
jimmykane Jan 30, 2026
cd48a79
chore: bump sl
jimmykane Jan 30, 2026
9510797
chore: email templts
jimmykane Jan 30, 2026
7e72e2d
chore: update update
jimmykane Jan 30, 2026
d4da81e
feature: add whats new
jimmykane Jan 30, 2026
af158b2
chore: updates
jimmykane Jan 30, 2026
27a666e
chore: improvements on UI and services
jimmykane Jan 30, 2026
88523ee
fix: tests
jimmykane Jan 30, 2026
48dd286
chore: rules test
jimmykane Jan 31, 2026
c3a52bd
refactor: whats new
jimmykane Jan 31, 2026
5550278
chore: improve intesity zones and hide z6 z7 if no data etc
jimmykane Jan 31, 2026
3d17c63
chore: reattach ids
jimmykane Jan 31, 2026
8c83786
chore: disable clicks for google maps
jimmykane Jan 31, 2026
e1a48dd
chore: bump sl
jimmykane Jan 31, 2026
b714b54
style: admin changelog ui
jimmykane Jan 31, 2026
48dc26a
fix: label
jimmykane Jan 31, 2026
1bc474d
chore: improve history form estimates
jimmykane Jan 31, 2026
887bc8c
chore: bump sl
jimmykane Jan 31, 2026
716da73
chore: exclude ascent
jimmykane Jan 31, 2026
8e67f3f
chore: exclude descent
jimmykane Jan 31, 2026
7aabb3f
fix: area hidden
jimmykane Jan 31, 2026
1c51e8e
fix: test
jimmykane Jan 31, 2026
c38f640
chore: decrease
jimmykane Jan 31, 2026
beb9186
chore: devices bottom sheet
jimmykane Feb 1, 2026
258a379
chore: history estimates
jimmykane Feb 1, 2026
1f0b23c
chore: grid now shows ascent and descent conditionally
jimmykane Feb 1, 2026
375da9f
chore: remove time bucketing for frontend
jimmykane Feb 1, 2026
4138253
chore: add tests
jimmykane Feb 1, 2026
f12470c
fix: exclude ascent and descent from grid
jimmykane Feb 1, 2026
3ba45b9
fix: grace period not updated
jimmykane Feb 1, 2026
003b336
refactor: frontend
jimmykane Feb 1, 2026
576b167
refactor: backend
jimmykane Feb 1, 2026
aff921a
chore: renames
jimmykane Feb 1, 2026
1f5cd6c
chore: add lock icon
jimmykane Feb 1, 2026
fca33ae
chore: prevent double fetch
jimmykane Feb 1, 2026
996f87f
fix: for instant roles
jimmykane Feb 1, 2026
bc8fe04
refactor: extract to utils
jimmykane Feb 1, 2026
60662a5
chore: tests
jimmykane Feb 2, 2026
64c0eac
chore: fixes
jimmykane Feb 2, 2026
ad84285
chore: tests fixes
jimmykane Feb 2, 2026
c2deb27
chore: styles
jimmykane Feb 2, 2026
7bd10d0
chore: improve token handling
jimmykane Feb 2, 2026
f1fe05d
fix: tests
jimmykane Feb 2, 2026
ccafeeb
chore: add resolve for whats new
jimmykane Feb 2, 2026
60b043f
feature: home ui
jimmykane Feb 2, 2026
b3d5d3c
chore: tests
jimmykane Feb 2, 2026
6eb41ab
chore: styles
jimmykane Feb 2, 2026
dd46039
Merge pull request #356 from jimmykane/refactor/claimsandroles
jimmykane Feb 2, 2026
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
7 changes: 4 additions & 3 deletions .agent/rules/rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,10 @@ trigger: always_on
- **Dependency Injection**:
- Supported: Constructor Injection (Legacy/Current).
- Preferred for New Code: `inject()` function.
- **Signals & Observables Naming**:
- **STRICT RULE**: Do **NOT** use the `$` suffix for Observables or Signals (e.g., use `isLoading`, not `isLoading$`). This applies to all variables.
- Reason: Consistency and readability, avoiding "Swiss cheese" code style.
- **Signals & Observables Naming**:
- **STRICT RULE**: **ALWAYS** use the `$` suffix for Observables (e.g., `user$`, `isLoading$`).
- **Signals**: Do **NOT** use the `$` suffix for Signals (e.g., `isLoading`, `user`).
- Reason: Clear distinction between streams (Observables) and reactive state (Signals).

### Firebase
- Use **Modular SDK** (`@angular/fire` v20+, `firebase` v9+).
Expand Down
5 changes: 2 additions & 3 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@
"src/sitemap.xml"
],
"styles": [
"./node_modules/material-design-icons-iconfont/dist/material-design-icons.css",
"./node_modules/leaflet/dist/leaflet.css",
"./node_modules/leaflet-fullscreen/dist/leaflet.fullscreen.css",
"./node_modules/material-symbols/rounded.css",
"./node_modules/mapbox-gl/dist/mapbox-gl.css",
"./src/styles.scss"
],
"scripts": [],
Expand Down
33 changes: 26 additions & 7 deletions firestore.indexes.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
{
"indexes": [
{
"collectionGroup": "changelogs",
"queryScope": "COLLECTION",
"fields": [
{
"fieldPath": "published",
"order": "ASCENDING"
},
{
"fieldPath": "date",
"order": "DESCENDING"
},
{
"fieldPath": "__name__",
"order": "DESCENDING"
}
],
"density": "SPARSE_ALL"
},
{
"collectionGroup": "COROSAPIWorkoutQueue",
"queryScope": "COLLECTION",
Expand Down Expand Up @@ -1087,8 +1106,8 @@
]
},
{
"collectionGroup": "tokens",
"fieldPath": "dateRefreshed",
"collectionGroup": "system",
"fieldPath": "gracePeriodUntil",
"ttl": false,
"indexes": [
{
Expand All @@ -1111,7 +1130,7 @@
},
{
"collectionGroup": "tokens",
"fieldPath": "openId",
"fieldPath": "dateRefreshed",
"ttl": false,
"indexes": [
{
Expand All @@ -1134,7 +1153,7 @@
},
{
"collectionGroup": "tokens",
"fieldPath": "userName",
"fieldPath": "openId",
"ttl": false,
"indexes": [
{
Expand All @@ -1156,8 +1175,8 @@
]
},
{
"collectionGroup": "system",
"fieldPath": "gracePeriodUntil",
"collectionGroup": "tokens",
"fieldPath": "userName",
"ttl": false,
"indexes": [
{
Expand All @@ -1179,4 +1198,4 @@
]
}
]
}
}
5 changes: 5 additions & 0 deletions firestore.rules
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,11 @@ service cloud.firestore {
allow read: if isAdmin();
allow write: if false;
}

match /changelogs/{docId} {
allow read: if resource.data.published == true || isAdmin();
allow write: if isAdmin();
}
}
}

Expand Down
16 changes: 8 additions & 8 deletions functions/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion functions/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"@google-cloud/billing": "^5.1.1",
"@google-cloud/billing-budgets": "^6.1.1",
"@google-cloud/tasks": "^6.2.1",
"@sports-alliance/sports-lib": "^7.2.2",
"@sports-alliance/sports-lib": "^8.0.5",
"blob": "^0.1.0",
"bs58": "^4.0.1",
"cors": "^2.8.5",
Expand Down
2 changes: 1 addition & 1 deletion functions/src/OAuth2.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@
isCorsAllowed: vi.fn().mockReturnValue(true),
setAccessControlHeadersOnResponse: vi.fn(),
getUserIDFromFirebaseToken: vi.fn().mockResolvedValue('testUserID'),
isProUser: vi.fn().mockResolvedValue(true),
hasProAccess: vi.fn().mockResolvedValue(true),
PRO_REQUIRED_MESSAGE: 'Service sync is a Pro feature.'
}));

Expand Down Expand Up @@ -586,7 +586,7 @@
expect(mockCollection).toHaveBeenCalledWith('garminAPITokens');

// Verify state and codeVerifier were saved
const setCall = (mockDocInstance.set as any).mock.calls[0][0];

Check warning on line 589 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / test / run_tests

Unexpected any. Specify a different type

Check warning on line 589 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / run-tests / run_tests

Unexpected any. Specify a different type
expect(setCall.state).toBeDefined();
expect(setCall.codeVerifier).toBeDefined();
});
Expand All @@ -604,7 +604,7 @@
mockGet.mockResolvedValue({
exists: true,
data: () => ({ state: correctState }),
} as any);

Check warning on line 607 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / test / run_tests

Unexpected any. Specify a different type

Check warning on line 607 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / run-tests / run_tests

Unexpected any. Specify a different type

const result = await validateOAuth2State(userID, ServiceNames.SuuntoApp, correctState);

Expand All @@ -615,7 +615,7 @@
mockGet.mockResolvedValue({
exists: true,
data: () => ({ state: 'different-state' }),
} as any);

Check warning on line 618 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / test / run_tests

Unexpected any. Specify a different type

Check warning on line 618 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / run-tests / run_tests

Unexpected any. Specify a different type

const result = await validateOAuth2State(userID, ServiceNames.SuuntoApp, correctState);

Expand All @@ -626,7 +626,7 @@
mockGet.mockResolvedValue({
exists: false,
data: () => undefined,
} as any);

Check warning on line 629 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / test / run_tests

Unexpected any. Specify a different type

Check warning on line 629 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / run-tests / run_tests

Unexpected any. Specify a different type

const result = await validateOAuth2State(userID, ServiceNames.SuuntoApp, correctState);

Expand Down Expand Up @@ -668,7 +668,7 @@
expect(result.serviceName).toBe(ServiceNames.GarminAPI);
expect(result.accessToken).toBe('garmin-access-token');
expect(result.refreshToken).toBe('garmin-refresh-token');
expect((result as any).userID).toBe('garmin-user-123');

Check warning on line 671 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / test / run_tests

Unexpected any. Specify a different type

Check warning on line 671 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / run-tests / run_tests

Unexpected any. Specify a different type
expect(result.dateCreated).toBeDefined();
expect(result.dateRefreshed).toBeDefined();
});
Expand All @@ -690,7 +690,7 @@
'override-user-id'
);

expect((result as any).userID).toBe('override-user-id');

Check warning on line 693 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / test / run_tests

Unexpected any. Specify a different type

Check warning on line 693 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / run-tests / run_tests

Unexpected any. Specify a different type
});

it('should use default token_type and scope for Garmin when missing', () => {
Expand Down Expand Up @@ -808,7 +808,7 @@

// Simulate 500 error from API
const error500 = new Error('Internal Server Error');
(error500 as any).statusCode = 500;

Check warning on line 811 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / test / run_tests

Unexpected any. Specify a different type

Check warning on line 811 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / run-tests / run_tests

Unexpected any. Specify a different type
(requestPromise.get as ReturnType<typeof vi.fn>).mockRejectedValue(error500);

await deauthorizeServiceForUser(userID, ServiceNames.SuuntoApp);
Expand Down Expand Up @@ -837,7 +837,7 @@

// Simulate 502 error from API
const error502 = new Error('Bad Gateway');
(error502 as any).statusCode = 502;

Check warning on line 840 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / test / run_tests

Unexpected any. Specify a different type

Check warning on line 840 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / run-tests / run_tests

Unexpected any. Specify a different type
(requestPromise.get as ReturnType<typeof vi.fn>).mockRejectedValue(error502);

await deauthorizeServiceForUser(userID, ServiceNames.SuuntoApp);
Expand Down Expand Up @@ -867,7 +867,7 @@

// Simulate 404 error from API
const error404 = new Error('Not Found');
(error404 as any).statusCode = 404;

Check warning on line 870 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / test / run_tests

Unexpected any. Specify a different type

Check warning on line 870 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / run-tests / run_tests

Unexpected any. Specify a different type
(requestPromise.get as ReturnType<typeof vi.fn>).mockRejectedValue(error404);

await deauthorizeServiceForUser(userID, ServiceNames.SuuntoApp);
Expand Down Expand Up @@ -917,7 +917,7 @@
data: () => ({}),
empty: true,
docs: [],
} as any);

Check warning on line 920 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / test / run_tests

Unexpected any. Specify a different type

Check warning on line 920 in functions/src/OAuth2.spec.ts

View workflow job for this annotation

GitHub Actions / run-tests / run_tests

Unexpected any. Specify a different type
});

it('should throw error when getToken returns no results', async () => {
Expand Down
60 changes: 60 additions & 0 deletions functions/src/config.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';

// Hoisted admin mock & dotenv noop
const adminMock = vi.hoisted(() => ({
instanceId: vi.fn(() => ({
app: { options: { projectId: 'mock-project' } }
}))
}));

vi.mock('dotenv', () => ({ config: vi.fn() }));

vi.mock('firebase-admin', () => ({
default: {
instanceId: adminMock.instanceId
},
instanceId: adminMock.instanceId
}));

const envBackup: NodeJS.ProcessEnv = { ...process.env };

describe('config.ts', () => {
beforeEach(() => {
vi.resetModules();
Object.assign(process.env, {
SUUNTOAPP_CLIENT_ID: 'suunto-id',
SUUNTOAPP_CLIENT_SECRET: 'suunto-secret',
SUUNTOAPP_SUBSCRIPTION_KEY: 'suunto-sub',
COROSAPI_CLIENT_ID: 'coros-id',
COROSAPI_CLIENT_SECRET: 'coros-secret',
GARMINAPI_CLIENT_ID: 'garmin-id',
GARMINAPI_CLIENT_SECRET: 'garmin-secret',
});
delete process.env.GCLOUD_PROJECT; // force fallback to admin.instanceId
});

afterEach(() => {
process.env = { ...envBackup };
vi.clearAllMocks();
});

it('returns configured values and derives cloudtasks defaults from admin project', async () => {
const { config } = await import('./config');

expect(config.suuntoapp.client_id).toBe('suunto-id');
expect(config.suuntoapp.subscription_key).toBe('suunto-sub');
expect(config.corosapi.client_secret).toBe('coros-secret');
expect(config.garminapi.client_id).toBe('garmin-id');

expect(config.cloudtasks.projectId).toBe('mock-project');
expect(config.cloudtasks.serviceAccountEmail).toBe('[email protected]');
expect(config.debug.bucketName).toBe('quantified-self-io-debug-files');
});

it('throws when a required env var is missing', async () => {
delete process.env.SUUNTOAPP_CLIENT_ID;
const { config } = await import('./config');

expect(() => config.suuntoapp.client_id).toThrow(/Missing required environment variable: SUUNTOAPP_CLIENT_ID/);
});
});
13 changes: 10 additions & 3 deletions functions/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,16 +27,18 @@ interface CloudTasksConfig {
serviceAccountEmail: string;
}

interface DebugConfig {
bucketName: string;
}

interface AppConfig {
suuntoapp: SuuntoAppConfig;
corosapi: CorosApiConfig;
garminapi: GarminApiConfig;
cloudtasks: CloudTasksConfig;

debug: DebugConfig;
}



function getEnvVar(name: string): string {
const value = process.env[name];
if (!value) {
Expand Down Expand Up @@ -74,4 +76,9 @@ export const config: AppConfig = {
serviceAccountEmail: `${process.env.GCLOUD_PROJECT || admin.instanceId().app.options.projectId}@appspot.gserviceaccount.com`,
};
},
get debug() {
return {
bucketName: 'quantified-self-io-debug-files',
};
},
};
8 changes: 4 additions & 4 deletions functions/src/coros/auth/wrapper.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ vi.mock('firebase-functions/v1', () => ({
}));

vi.mock('../../utils', () => ({
isProUser: vi.fn().mockResolvedValue(true),
hasProAccess: vi.fn().mockResolvedValue(true),
PRO_REQUIRED_MESSAGE: 'Service sync is a Pro feature.'
}));

Expand All @@ -52,7 +52,7 @@ describe('COROS Auth Wrapper', () => {

beforeEach(() => {
vi.clearAllMocks();
(utils.isProUser as any).mockResolvedValue(true);
(utils.hasProAccess as any).mockResolvedValue(true);

context = {
app: { appId: 'test-app' },
Expand All @@ -67,7 +67,7 @@ describe('COROS Auth Wrapper', () => {
it('should return redirect URI for pro user', async () => {
const result = await getCOROSAPIAuthRequestTokenRedirectURI(data, context);

expect(utils.isProUser).toHaveBeenCalledWith('testUserID');
expect(utils.hasProAccess).toHaveBeenCalledWith('testUserID');
expect(oauth2.getServiceOAuth2CodeRedirectAndSaveStateToUser).toHaveBeenCalledWith(
'testUserID',
SERVICE_NAME,
Expand All @@ -77,7 +77,7 @@ describe('COROS Auth Wrapper', () => {
});

it('should throw error for non-pro user', async () => {
(utils.isProUser as any).mockResolvedValue(false);
(utils.hasProAccess as any).mockResolvedValue(false);

await expect(getCOROSAPIAuthRequestTokenRedirectURI(data, context))
.rejects.toThrow('Service sync is a Pro feature.');
Expand Down
6 changes: 3 additions & 3 deletions functions/src/coros/auth/wrapper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import * as functions from 'firebase-functions/v1';
import * as logger from 'firebase-functions/logger';
import { isProUser, PRO_REQUIRED_MESSAGE } from '../../utils';
import { hasProAccess, PRO_REQUIRED_MESSAGE } from '../../utils';
import {
deauthorizeServiceForUser,
getAndSetServiceOAuth2AccessTokenForUser,
Expand Down Expand Up @@ -38,7 +38,7 @@ export const getCOROSAPIAuthRequestTokenRedirectURI = functions
const userID = context.auth.uid;

// Enforce Pro Access
if (!(await isProUser(userID))) {
if (!(await hasProAccess(userID))) {
logger.warn(`Blocking COROS Auth for non-pro user ${userID}`);
throw new functions.https.HttpsError('permission-denied', PRO_REQUIRED_MESSAGE);
}
Expand Down Expand Up @@ -77,7 +77,7 @@ export const requestAndSetCOROSAPIAccessToken = functions
const userID = context.auth.uid;

// Enforce Pro Access
if (!(await isProUser(userID))) {
if (!(await hasProAccess(userID))) {
logger.warn(`Blocking COROS Token Set for non-pro user ${userID}`);
throw new functions.https.HttpsError('permission-denied', PRO_REQUIRED_MESSAGE);
}
Expand Down
6 changes: 3 additions & 3 deletions functions/src/coros/history-to-queue.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ vi.mock('firebase-functions/v1', () => ({
}));

vi.mock('../utils', () => ({
isProUser: vi.fn().mockResolvedValue(true),
hasProAccess: vi.fn().mockResolvedValue(true),
PRO_REQUIRED_MESSAGE: 'Service sync is a Pro feature.'
}));

Expand All @@ -47,7 +47,7 @@ describe('COROS History to Queue', () => {

beforeEach(() => {
vi.clearAllMocks();
(utils.isProUser as any).mockResolvedValue(true);
(utils.hasProAccess as any).mockResolvedValue(true);
(history.getNextAllowedHistoryImportDate as any).mockResolvedValue(null);

const recentDate = new Date();
Expand Down Expand Up @@ -177,7 +177,7 @@ describe('COROS History to Queue', () => {
});

it('should throw error for non-pro user', async () => {
(utils.isProUser as any).mockResolvedValue(false);
(utils.hasProAccess as any).mockResolvedValue(false);

await expect(addCOROSAPIHistoryToQueue(data, context))
.rejects.toThrow('Service sync is a Pro feature.');
Expand Down
4 changes: 2 additions & 2 deletions functions/src/coros/history-to-queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import * as functions from 'firebase-functions/v1';
import * as logger from 'firebase-functions/logger';
import { isProUser, PRO_REQUIRED_MESSAGE } from '../utils';
import { hasProAccess, PRO_REQUIRED_MESSAGE } from '../utils';
import { SERVICE_NAME } from './constants';
import { COROS_HISTORY_IMPORT_LIMIT_MONTHS } from '../shared/history-import.constants';
import { HistoryImportResult, addHistoryToQueue, getNextAllowedHistoryImportDate } from '../history';
Expand Down Expand Up @@ -38,7 +38,7 @@ export const addCOROSAPIHistoryToQueue = functions
const userID = context.auth.uid;

// Enforce Pro Access
if (!(await isProUser(userID))) {
if (!(await hasProAccess(userID))) {
logger.warn(`Blocking history import for non-pro user ${userID}`);
throw new functions.https.HttpsError('permission-denied', PRO_REQUIRED_MESSAGE);
}
Expand Down
Loading
Loading