diff --git a/.aiassistant/rules/guidelines.md b/.aiassistant/rules/guidelines.md
index 884c6125..4ef3b760 100644
--- a/.aiassistant/rules/guidelines.md
+++ b/.aiassistant/rules/guidelines.md
@@ -3,25 +3,27 @@ apply: by model decision
---
---
+
trigger: always_on
description:
globs:
+
---
## Project Structure
- next.js react tailwind frontend `/web`
- - broken down into pages, components, hooks, lib
+ - broken down into pages, components, hooks, lib
- express node api server `/backend/api`
- one off scripts, like migrations `/backend/scripts`
- supabase postgres. schema in `/backend/supabase`
- - supabase-generated types in `/backend/supabase/schema.ts`
+ - supabase-generated types in `/backend/supabase/schema.ts`
- files shared between backend directories `/backend/shared`
- - anything in `/backend` can import from `shared`, but not vice versa
+ - anything in `/backend` can import from `shared`, but not vice versa
- files shared between the frontend and backend in `/common`
- - `/common` has lots of type definitions for our data structures, like User. It also contains many useful utility
- functions. We try not to add package dependencies to common. `/web` and `/backend` are allowed to import from
- `/common`, but not vice versa.
+ - `/common` has lots of type definitions for our data structures, like User. It also contains many useful utility
+ functions. We try not to add package dependencies to common. `/web` and `/backend` are allowed to import from
+ `/common`, but not vice versa.
## Deployment
@@ -56,18 +58,11 @@ export function HeadlineTabs(props: {
notSticky?: boolean
className?: string
}) {
- const {headlines, endpoint, currentSlug, hideEmoji, notSticky, className} =
- props
+ const {headlines, endpoint, currentSlug, hideEmoji, notSticky, className} = props
const user = useUser()
return (
-
+
{headlines.map(({id, slug, title}) => (
))}
- {user && }
+ {user && }
{user && (isAdminId(user.id) || isModId(user.id)) && (
-
+
)}
@@ -150,9 +145,7 @@ Here's the definition of usePersistentInMemoryState:
```ts
export const usePersistentInMemoryState =
(initialValue: T, key: string) => {
- const [state, setState] = useStateCheckEquality(
- safeJsonParse(store[key]) ?? initialValue
- )
+ const [state, setState] = useStateCheckEquality(safeJsonParse(store[key]) ?? initialValue)
useEffect(() => {
const storedValue = safeJsonParse(store[key]) ?? initialValue
@@ -196,7 +189,7 @@ In `use-bets`, we have this hook to get live updates with useApiSubscription:
```ts
export const useContractBets = (
contractId: string,
- opts?: APIParams<'bets'> & { enabled?: boolean }
+ opts?: APIParams<'bets'> & {enabled?: boolean},
) => {
const {enabled = true, ...apiOptions} = {
contractId,
@@ -204,17 +197,11 @@ export const useContractBets = (
}
const optionsKey = JSON.stringify(apiOptions)
- const [newBets, setNewBets] = usePersistentInMemoryState(
- [],
- `${optionsKey}-bets`
- )
+ const [newBets, setNewBets] = usePersistentInMemoryState([], `${optionsKey}-bets`)
const addBets = (bets: Bet[]) => {
setNewBets((currentBets) => {
- const uniqueBets = sortBy(
- uniqBy([...currentBets, ...bets], 'id'),
- 'createdTime'
- )
+ const uniqueBets = sortBy(uniqBy([...currentBets, ...bets], 'id'), 'createdTime')
return uniqueBets.filter((b) => !betShouldBeFiltered(b, apiOptions))
})
}
@@ -249,7 +236,7 @@ export function broadcastUpdatedPrivateUser(userId: string) {
broadcast(`private-user/${userId}`, {})
}
-export function broadcastUpdatedUser(user: Partial & { id: string }) {
+export function broadcastUpdatedUser(user: Partial & {id: string}) {
broadcast(`user/${user.id}`, {user})
}
@@ -334,7 +321,7 @@ export const placeBet: APIHandler<'bet'> = async (props, auth) => {
const isApi = auth.creds.kind === 'key'
return await betsQueue.enqueueFn(
() => placeBetMain(props, auth.uid, isApi),
- [props.contractId, auth.uid]
+ [props.contractId, auth.uid],
)
}
```
@@ -380,13 +367,10 @@ using the pg-promise library. The client (code in web) does not have permission
Another example using the direct client:
```ts
-export const getUniqueBettorIds = async (
- contractId: string,
- pg: SupabaseDirectClient
-) => {
+export const getUniqueBettorIds = async (contractId: string, pg: SupabaseDirectClient) => {
const res = await pg.manyOrNone(
'select distinct user_id from contract_bets where contract_id = $1',
- [contractId]
+ [contractId],
)
return res.map((r) => r.user_id as string)
}
@@ -441,7 +425,7 @@ const query = renderSql(
from('contract_bets'),
where('contract_id = ${id}', {id}),
orderBy('created_time desc'),
- limitValue != null && limit(limitValue)
+ limitValue != null && limit(limitValue),
)
const res = await pg.manyOrNone(query)
@@ -480,7 +464,7 @@ can do so via this SQL command (change the type if not `TEXT`):
```sql
ALTER TABLE profiles
- ADD COLUMN profile_field TEXT;
+ADD COLUMN profile_field TEXT;
```
Store it in `add_profile_field.sql` in the [migrations](../backend/supabase/migrations) folder and
@@ -544,28 +528,28 @@ How we apply it here
This project uses three complementary test types. Use the right level for the job:
- Unit tests
- - Purpose: Verify a single function/module in isolation; fast, deterministic.
- - Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
- etc.).
- - Runner: Jest (configured via root `jest.config.js`).
- - Naming: `*.unit.test.ts` (or `.tsx` for React in `web`).
- - When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
+ - Purpose: Verify a single function/module in isolation; fast, deterministic.
+ - Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
+ etc.).
+ - Runner: Jest (configured via root `jest.config.js`).
+ - Naming: `*.unit.test.ts` (or `.tsx` for React in `web`).
+ - When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
- Integration tests
- - Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
- spinning up the full app.
- - Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).
- - Runner: Jest (configured via root `jest.config.js`).
- - Naming: `*.integration.test.ts` (or `.tsx` for React in `web`).
- - When to use: Boundaries between modules, real serialization/parsing, API handlers with mocked network/DB,
- component trees with providers.
+ - Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
+ spinning up the full app.
+ - Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).
+ - Runner: Jest (configured via root `jest.config.js`).
+ - Naming: `*.integration.test.ts` (or `.tsx` for React in `web`).
+ - When to use: Boundaries between modules, real serialization/parsing, API handlers with mocked network/DB,
+ component trees with providers.
- End-to-End (E2E) tests
- - Purpose: Validate real user flows across the full stack.
- - Where: Top-level `tests/e2e` with separate areas for `web` and `backend`.
- - Runner: Playwright (see root `playwright.config.ts`, `testDir: ./tests/e2e`).
- - Naming: `*.e2e.spec.ts`.
- - When to use: Critical journeys (signup, login, checkout), cross-service interactions, smoke tests for deployments.
+ - Purpose: Validate real user flows across the full stack.
+ - Where: Top-level `tests/e2e` with separate areas for `web` and `backend`.
+ - Runner: Playwright (see root `playwright.config.ts`, `testDir: ./tests/e2e`).
+ - Naming: `*.e2e.spec.ts`.
+ - When to use: Critical journeys (signup, login, checkout), cross-service interactions, smoke tests for deployments.
Quick commands
@@ -635,23 +619,23 @@ web/
- Unit and integration tests live in each package’s `tests` folder and are executed by Jest via the root
`jest.config.js` projects array.
- Naming:
- - Unit: `*.unit.test.ts` (or `.tsx` for React in `web`)
- - Integration: `*.integration.test.ts`
- - E2E (Playwright): `*.e2e.spec.ts`
+ - Unit: `*.unit.test.ts` (or `.tsx` for React in `web`)
+ - Integration: `*.integration.test.ts`
+ - E2E (Playwright): `*.e2e.spec.ts`
### Best Practices
-* Test Behavior, Not Implementation. Don’t test internal state or function calls unless you’re testing utilities or very
+- Test Behavior, Not Implementation. Don’t test internal state or function calls unless you’re testing utilities or very
critical behavior.
-* Use msw to Mock APIs. Don't manually mock fetch—use msw to simulate realistic behavior, including network delays and
+- Use msw to Mock APIs. Don't manually mock fetch—use msw to simulate realistic behavior, including network delays and
errors.
-* Don’t Overuse Snapshots. Snapshots are fragile and often meaningless unless used sparingly (e.g., for JSON response
+- Don’t Overuse Snapshots. Snapshots are fragile and often meaningless unless used sparingly (e.g., for JSON response
schemas).
-* Prefer userEvent Over fireEvent. It simulates real user interactions more accurately.
-* Avoid Testing Next.js Internals . You don’t need to test getStaticProps, getServerSideProps themselves-test what they
+- Prefer userEvent Over fireEvent. It simulates real user interactions more accurately.
+- Avoid Testing Next.js Internals . You don’t need to test getStaticProps, getServerSideProps themselves-test what they
render.
-* Don't test just for coverage. Test to prevent regressions, document intent, and handle edge cases.
-* Don't write end-to-end tests for features that change frequently unless absolutely necessary.
+- Don't test just for coverage. Test to prevent regressions, document intent, and handle edge cases.
+- Don't write end-to-end tests for features that change frequently unless absolutely necessary.
### Jest Unit Testing Guide
@@ -679,14 +663,14 @@ yarn test path/to/test.unit.test.ts
#### Test Standards
- Test file names should convey what to expect
- - Follow the pattern: `.[unit,integration].test.ts`. Examples:
- - filename.unit.test.ts
- - filename.integration.test.ts
+ - Follow the pattern: `.[unit,integration].test.ts`. Examples:
+ - filename.unit.test.ts
+ - filename.integration.test.ts
- Group related tests using describe blocks
- Use descriptive test names that explain the expected behavior.
- - Follow the pattern: "should `expected behavior` [relevant modifier]". Examples:
- - should `ban user` [with matching user id]
- - should `ban user` [with matching user name]
+ - Follow the pattern: "should `expected behavior` [relevant modifier]". Examples:
+ - should `ban user` [with matching user id]
+ - should `ban user` [with matching user name]
#### Mocking
@@ -730,15 +714,15 @@ When writing mocks, assert both outcome and interaction:
Why mocking is important?
-- *Isolation* - Test your code independently of databases, APIs, and external systems. Tests only fail when your code
+- _Isolation_ - Test your code independently of databases, APIs, and external systems. Tests only fail when your code
breaks, not when a server is down.
-- *Speed* - Mocked tests run in milliseconds vs. seconds for real network/database calls. Run your suite constantly
+- _Speed_ - Mocked tests run in milliseconds vs. seconds for real network/database calls. Run your suite constantly
without waiting.
-- *Control* - Easily simulate edge cases like API errors, timeouts, or rare conditions that are difficult to reproduce
+- _Control_ - Easily simulate edge cases like API errors, timeouts, or rare conditions that are difficult to reproduce
with real systems.
-- *Reliability* - Eliminate unpredictable failures from network issues, rate limits, or changing external data. Same
+- _Reliability_ - Eliminate unpredictable failures from network issues, rate limits, or changing external data. Same
inputs = same results, every time.
-- *Focus* - Verify your function's logic and how it uses its dependencies, without requiring those dependencies to
+- _Focus_ - Verify your function's logic and how it uses its dependencies, without requiring those dependencies to
actually work yet.
###### Use `jest.mock()`
@@ -751,40 +735,40 @@ function’s return value isn’t used, there’s no need to mock it further.
```tsx
//Function and module mocks
-jest.mock('path/to/module');
+jest.mock('path/to/module')
//Function and module imports
-import {functionUnderTest} from "path/to/function"
-import {module} from "path/to/module"
+import {functionUnderTest} from 'path/to/function'
+import {module} from 'path/to/module'
describe('functionUnderTest', () => {
//Setup
beforeEach(() => {
//Run before each test
- jest.resetAllMocks(); // Resets any mocks from previous tests
- });
+ jest.resetAllMocks() // Resets any mocks from previous tests
+ })
afterEach(() => {
//Run after each test
- jest.restoreAllMocks(); // Cleans up between tests
- });
+ jest.restoreAllMocks() // Cleans up between tests
+ })
describe('when given valid input', () => {
it('should describe what is being tested', async () => {
//Arrange: Setup test data
- const mockData = 'test';
+ const mockData = 'test'
//Act: Execute the function under test
- const result = myFunction(mockData);
+ const result = myFunction(mockData)
//Assert: Verify the result
- expect(result).toBe('expected');
- });
- });
+ expect(result).toBe('expected')
+ })
+ })
describe('when an error occurs', () => {
//Test cases for errors
- });
-});
+ })
+})
```
###### Modules
@@ -794,27 +778,27 @@ called and what it was called with.
```tsx
//functionFile.ts
-import {module as mockedDep} from "path/to/module"
+import {module as mockedDep} from 'path/to/module'
export const functionUnderTest = async (param) => {
- return await mockedDep(param);
-};
+ return await mockedDep(param)
+}
```
```tsx
//testFile.unit.test.ts
-import {functionUnderTest} from "path/to/function";
-import {module as mockedDep} from "path/to/module";
+import {functionUnderTest} from 'path/to/function'
+import {module as mockedDep} from 'path/to/module'
-jest.mock('path/to/module');
+jest.mock('path/to/module')
/**
* Inside the test case
* We create a mock for any information passed into the function that is being tested
* and if the function returns a result we create a mock to test the result
*/
-const mockParam = "mockParam";
-const mockReturnValue = "mockModuleValue";
+const mockParam = 'mockParam'
+const mockReturnValue = 'mockModuleValue'
/**
* use .mockResolvedValue when handling async/await modules that return values
@@ -822,15 +806,15 @@ const mockReturnValue = "mockModuleValue";
*/
describe('functionUnderTest', () => {
it('returns mocked module value and calls dependency correctly', async () => {
- (mockedDep as jest.Mock).mockResolvedValue(mockReturnValue);
+ ;(mockedDep as jest.Mock).mockResolvedValue(mockReturnValue)
- const result = await functionUnderTest(mockParam);
+ const result = await functionUnderTest(mockParam)
- expect(result).toBe(mockReturnValue);
- expect(mockedDep).toHaveBeenCalledTimes(1);
- expect(mockedDep).toHaveBeenCalledWith(mockParam);
- });
-});
+ expect(result).toBe(mockReturnValue)
+ expect(mockedDep).toHaveBeenCalledTimes(1)
+ expect(mockedDep).toHaveBeenCalledWith(mockParam)
+ })
+})
```
Use namespace imports when you want to import everything a module exports under a single name.
@@ -838,37 +822,36 @@ Use namespace imports when you want to import everything a module exports under
```tsx
//moduleFile.ts
export const module = async (param) => {
- const value = "module"
+ const value = 'module'
return value
-};
+}
export const moduleTwo = async (param) => {
- const value = "moduleTwo"
+ const value = 'moduleTwo'
return value
-};
+}
```
```tsx
//functionFile.ts
-import {module, moduleTwo} from "path/to/module"
+import {module, moduleTwo} from 'path/to/module'
export const functionUnderTest = async (param) => {
const mockValue = await moduleTwo(param)
const returnValue = await module(mockValue)
- return returnValue;
-};
+ return returnValue
+}
```
```tsx
//testFile.unit.test.ts
-jest.mock('path/to/module');
+jest.mock('path/to/module')
/**
* This creates an object containing all named exports from ./path/to/module
*/
-import * as mockModule from "path/to/module"
-
-(mockModule.module as jest.Mock).mockResolvedValue(mockReturnValue);
+import * as mockModule from 'path/to/module'
+;(mockModule.module as jest.Mock).mockResolvedValue(mockReturnValue)
```
When mocking modules, you can use `jest.spyOn()` instead of `jest.mock()`.
@@ -876,21 +859,21 @@ When mocking modules, you can use `jest.spyOn()` instead of `jest.mock()`.
- `jest.mock()` mocks the entire module, which is ideal for external dependencies like Axios or database clients.
- `jest.spyOn()` mocks specific methods while keeping the real implementation for others. It can also be used to observe
how a real method is called without changing its behavior.
- - also replaces the need to have `jest.mock()` at the top of the file.
+ - also replaces the need to have `jest.mock()` at the top of the file.
```tsx
//testFile.unit.test.ts
-import * as mockModule from "path/to/module"
+import * as mockModule from 'path/to/module'
//Mocking the return value of the module
-jest.spyOn(mockModule, 'module').mockResolvedValue(mockReturnValue);
+jest.spyOn(mockModule, 'module').mockResolvedValue(mockReturnValue)
//Spying on the module to check functionality
-jest.spyOn(mockModule, 'module');
+jest.spyOn(mockModule, 'module')
//You can assert the module functionality with both of the above exactly like you would if you used jest.mock()
-expect(mockModule.module).toBeCalledTimes(1);
-expect(mockModule.module).toBeCalledWith(mockParam);
+expect(mockModule.module).toBeCalledTimes(1)
+expect(mockModule.module).toBeCalledWith(mockParam)
```
###### Dependencies
@@ -900,119 +883,114 @@ external functionality.
```tsx
//functionFile.ts
-import {dependency} from "path/to/dependency"
+import {dependency} from 'path/to/dependency'
export const functionUnderTest = async (param) => {
- const depen = await dependency();
- const value = depen.module();
+ const depen = await dependency()
+ const value = depen.module()
- return value;
-};
+ return value
+}
```
```tsx
//testFile.unit.test.ts
-jest.mock('path/to/dependency');
+jest.mock('path/to/dependency')
-import {dependency} from "path/to/dependency"
+import {dependency} from 'path/to/dependency'
describe('functionUnderTest', () => {
/**
* Because the dependency has modules that are used we need to
* create a variable outside of scope that can be asserted on
*/
- let mockDependency = {} as any;
+ let mockDependency = {} as any
beforeEach(() => {
mockDependency = {
module: jest.fn(),
- };
- jest.resetAllMocks(); // Resets any mocks from previous tests
- });
+ }
+ jest.resetAllMocks() // Resets any mocks from previous tests
+ })
afterEach(() => {
//Run after each test
- jest.restoreAllMocks(); // Cleans up between tests
- });
+ jest.restoreAllMocks() // Cleans up between tests
+ })
//Inside the test case
- (mockDependency.module as jest.Mock).mockResolvedValue(mockReturnValue);
+ ;(mockDependency.module as jest.Mock).mockResolvedValue(mockReturnValue)
- expect(mockDependency.module).toBeCalledTimes(1);
- expect(mockDependency.module).toBeCalledWith(mockParam);
-});
+ expect(mockDependency.module).toBeCalledTimes(1)
+ expect(mockDependency.module).toBeCalledWith(mockParam)
+})
```
###### Error checking
```tsx
//function.ts
-const result = await functionName(param);
+const result = await functionName(param)
if (!result) {
- throw new Error(403, 'Error text', error);
+ throw new Error(403, 'Error text', error)
}
-;
```
```tsx
//testFile.unit.test.ts
-const mockParam = {} as any;
+const mockParam = {} as any
//This will check only the error message
-expect(functionName(mockParam))
- .rejects
- .toThrowError('Error text');
+expect(functionName(mockParam)).rejects.toThrowError('Error text')
//This will check the complete error
try {
- await functionName(mockParam);
- fail('Should have thrown');
+ await functionName(mockParam)
+ fail('Should have thrown')
} catch (error) {
- const functionError = error as Error;
- expect(functionError.code).toBe(403);
- expect(functionError.message).toBe('Error text');
- expect(functionError.details).toBe(mockParam);
- expect(functionError.name).toBe('Error');
+ const functionError = error as Error
+ expect(functionError.code).toBe(403)
+ expect(functionError.message).toBe('Error text')
+ expect(functionError.details).toBe(mockParam)
+ expect(functionError.name).toBe('Error')
}
```
```tsx
//For console.error types
-console.error('Error message', error);
+console.error('Error message', error)
//Use spyOn to mock
-const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {
-});
+const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
expect(errorSpy).toHaveBeenCalledWith(
'Error message',
- expect.objectContaining({name: 'Error'}) //The error 'name' refers to the error type
-);
-
+ expect.objectContaining({name: 'Error'}), //The error 'name' refers to the error type
+)
```
###### Mocking array return value
```tsx
//arrayFile.ts
-const exampleArray = [1, 2, 3, 4, 5];
+const exampleArray = [1, 2, 3, 4, 5]
-const arrayResult = exampleArray.includes(2);
+const arrayResult = exampleArray.includes(2)
```
```tsx
//testFile.unit.test.ts
//This will mock 'includes' for all arrays and force the return value to be true
-jest.spyOn(Array.prototype, 'includes').mockReturnValue(true);
+jest.spyOn(Array.prototype, 'includes').mockReturnValue(true)
// ---
//This will specify which 'includes' array to mock based on the args passed into the .includes()
jest.spyOn(Array.prototype, 'includes').mockImplementation(function (value) {
if (value === 2) {
- return true;
+ return true
}
- return false;
-});
+ return false
+})
```
### Playwright (E2E) Testing Guide
@@ -1038,23 +1016,26 @@ yarn test:db:reset
Use this priority order for selecting elements in Playwright tests:
1. Prefer `getByRole()` — use semantic roles that reflect how users interact
+
```typescript
- await page.getByRole('button', { name: 'Submit' }).click();
+ await page.getByRole('button', {name: 'Submit'}).click()
```
+
If a meaningful ARIA role is not available, fall back to accessible text selectors (next point).
2. Use accessible text selectors — when roles don't apply, target user-facing text
+
```typescript
- await page.getByLabel('Email').fill('user@example.com');
- await page.getByPlaceholder('Enter your name').fill('John');
- await page.getByText('Welcome back').isVisible();
+ await page.getByLabel('Email').fill('user@example.com')
+ await page.getByPlaceholder('Enter your name').fill('John')
+ await page.getByText('Welcome back').isVisible()
```
3. Only use `data-testid` — when elements have no stable user-facing text
```typescript
// For icons, toggles, or dynamic content without text
- await page.getByTestId('menu-toggle').click();
- await page.getByTestId('loading-spinner').isVisible();
+ await page.getByTestId('menu-toggle').click()
+ await page.getByTestId('loading-spinner').isVisible()
```
This hierarchy mirrors how users actually interact with your application, making tests more reliable and meaningful.
diff --git a/.github/ISSUE_TEMPLATE/other.yml b/.github/ISSUE_TEMPLATE/other.yml
index 72d05b0a..61d5b1e9 100644
--- a/.github/ISSUE_TEMPLATE/other.yml
+++ b/.github/ISSUE_TEMPLATE/other.yml
@@ -12,9 +12,9 @@ body:
attributes:
label: Info
description: |
- - Browser: [e.g. chrome, safari]
- - Device (if mobile): [e.g. iPhone6]
- - Build info
+ - Browser: [e.g. chrome, safari]
+ - Device (if mobile): [e.g. iPhone6]
+ - Build info
placeholder: |
Build info from `Settings` -> `About`
validations:
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index aa447661..047fd57d 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -4,5 +4,5 @@
- [ ] Tests added and passed if fixing a bug or adding a new feature.
### Description
-
+
diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md
index c939b721..e6f75f32 100644
--- a/.github/copilot-instructions.md
+++ b/.github/copilot-instructions.md
@@ -7,17 +7,17 @@ globs:
## Project Structure
- next.js react tailwind frontend `/web`
- - broken down into pages, components, hooks, lib
+ - broken down into pages, components, hooks, lib
- express node api server `/backend/api`
- one off scripts, like migrations `/backend/scripts`
- supabase postgres. schema in `/backend/supabase`
- - supabase-generated types in `/backend/supabase/schema.ts`
+ - supabase-generated types in `/backend/supabase/schema.ts`
- files shared between backend directories `/backend/shared`
- - anything in `/backend` can import from `shared`, but not vice versa
+ - anything in `/backend` can import from `shared`, but not vice versa
- files shared between the frontend and backend in `/common`
- - `/common` has lots of type definitions for our data structures, like User. It also contains many useful utility
- functions. We try not to add package dependencies to common. `/web` and `/backend` are allowed to import from
- `/common`, but not vice versa.
+ - `/common` has lots of type definitions for our data structures, like User. It also contains many useful utility
+ functions. We try not to add package dependencies to common. `/web` and `/backend` are allowed to import from
+ `/common`, but not vice versa.
## Deployment
@@ -52,18 +52,11 @@ export function HeadlineTabs(props: {
notSticky?: boolean
className?: string
}) {
- const {headlines, endpoint, currentSlug, hideEmoji, notSticky, className} =
- props
+ const {headlines, endpoint, currentSlug, hideEmoji, notSticky, className} = props
const user = useUser()
return (
-
+
{headlines.map(({id, slug, title}) => (
))}
- {user && }
+ {user && }
{user && (isAdminId(user.id) || isModId(user.id)) && (
-
+
)}
@@ -146,9 +139,7 @@ Here's the definition of usePersistentInMemoryState:
```ts
export const usePersistentInMemoryState =
(initialValue: T, key: string) => {
- const [state, setState] = useStateCheckEquality(
- safeJsonParse(store[key]) ?? initialValue
- )
+ const [state, setState] = useStateCheckEquality(safeJsonParse(store[key]) ?? initialValue)
useEffect(() => {
const storedValue = safeJsonParse(store[key]) ?? initialValue
@@ -192,7 +183,7 @@ In `use-bets`, we have this hook to get live updates with useApiSubscription:
```ts
export const useContractBets = (
contractId: string,
- opts?: APIParams<'bets'> & { enabled?: boolean }
+ opts?: APIParams<'bets'> & {enabled?: boolean},
) => {
const {enabled = true, ...apiOptions} = {
contractId,
@@ -200,17 +191,11 @@ export const useContractBets = (
}
const optionsKey = JSON.stringify(apiOptions)
- const [newBets, setNewBets] = usePersistentInMemoryState(
- [],
- `${optionsKey}-bets`
- )
+ const [newBets, setNewBets] = usePersistentInMemoryState([], `${optionsKey}-bets`)
const addBets = (bets: Bet[]) => {
setNewBets((currentBets) => {
- const uniqueBets = sortBy(
- uniqBy([...currentBets, ...bets], 'id'),
- 'createdTime'
- )
+ const uniqueBets = sortBy(uniqBy([...currentBets, ...bets], 'id'), 'createdTime')
return uniqueBets.filter((b) => !betShouldBeFiltered(b, apiOptions))
})
}
@@ -245,7 +230,7 @@ export function broadcastUpdatedPrivateUser(userId: string) {
broadcast(`private-user/${userId}`, {})
}
-export function broadcastUpdatedUser(user: Partial & { id: string }) {
+export function broadcastUpdatedUser(user: Partial & {id: string}) {
broadcast(`user/${user.id}`, {user})
}
@@ -330,7 +315,7 @@ export const placeBet: APIHandler<'bet'> = async (props, auth) => {
const isApi = auth.creds.kind === 'key'
return await betsQueue.enqueueFn(
() => placeBetMain(props, auth.uid, isApi),
- [props.contractId, auth.uid]
+ [props.contractId, auth.uid],
)
}
```
@@ -376,13 +361,10 @@ using the pg-promise library. The client (code in web) does not have permission
Another example using the direct client:
```ts
-export const getUniqueBettorIds = async (
- contractId: string,
- pg: SupabaseDirectClient
-) => {
+export const getUniqueBettorIds = async (contractId: string, pg: SupabaseDirectClient) => {
const res = await pg.manyOrNone(
'select distinct user_id from contract_bets where contract_id = $1',
- [contractId]
+ [contractId],
)
return res.map((r) => r.user_id as string)
}
@@ -437,7 +419,7 @@ const query = renderSql(
from('contract_bets'),
where('contract_id = ${id}', {id}),
orderBy('created_time desc'),
- limitValue != null && limit(limitValue)
+ limitValue != null && limit(limitValue),
)
const res = await pg.manyOrNone(query)
@@ -476,7 +458,7 @@ can do so via this SQL command (change the type if not `TEXT`):
```sql
ALTER TABLE profiles
- ADD COLUMN profile_field TEXT;
+ADD COLUMN profile_field TEXT;
```
Store it in `add_profile_field.sql` in the [migrations](../backend/supabase/migrations) folder and
@@ -540,28 +522,28 @@ How we apply it here
This project uses three complementary test types. Use the right level for the job:
- Unit tests
- - Purpose: Verify a single function/module in isolation; fast, deterministic.
- - Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
- etc.).
- - Runner: Jest (configured via root `jest.config.js`).
- - Naming: `*.unit.test.ts` (or `.tsx` for React in `web`).
- - When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
+ - Purpose: Verify a single function/module in isolation; fast, deterministic.
+ - Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
+ etc.).
+ - Runner: Jest (configured via root `jest.config.js`).
+ - Naming: `*.unit.test.ts` (or `.tsx` for React in `web`).
+ - When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
- Integration tests
- - Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
- spinning up the full app.
- - Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).
- - Runner: Jest (configured via root `jest.config.js`).
- - Naming: `*.integration.test.ts` (or `.tsx` for React in `web`).
- - When to use: Boundaries between modules, real serialization/parsing, API handlers with mocked network/DB,
- component trees with providers.
+ - Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
+ spinning up the full app.
+ - Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).
+ - Runner: Jest (configured via root `jest.config.js`).
+ - Naming: `*.integration.test.ts` (or `.tsx` for React in `web`).
+ - When to use: Boundaries between modules, real serialization/parsing, API handlers with mocked network/DB,
+ component trees with providers.
- End-to-End (E2E) tests
- - Purpose: Validate real user flows across the full stack.
- - Where: Top-level `tests/e2e` with separate areas for `web` and `backend`.
- - Runner: Playwright (see root `playwright.config.ts`, `testDir: ./tests/e2e`).
- - Naming: `*.e2e.spec.ts`.
- - When to use: Critical journeys (signup, login, checkout), cross-service interactions, smoke tests for deployments.
+ - Purpose: Validate real user flows across the full stack.
+ - Where: Top-level `tests/e2e` with separate areas for `web` and `backend`.
+ - Runner: Playwright (see root `playwright.config.ts`, `testDir: ./tests/e2e`).
+ - Naming: `*.e2e.spec.ts`.
+ - When to use: Critical journeys (signup, login, checkout), cross-service interactions, smoke tests for deployments.
Quick commands
@@ -631,23 +613,23 @@ web/
- Unit and integration tests live in each package’s `tests` folder and are executed by Jest via the root
`jest.config.js` projects array.
- Naming:
- - Unit: `*.unit.test.ts` (or `.tsx` for React in `web`)
- - Integration: `*.integration.test.ts`
- - E2E (Playwright): `*.e2e.spec.ts`
+ - Unit: `*.unit.test.ts` (or `.tsx` for React in `web`)
+ - Integration: `*.integration.test.ts`
+ - E2E (Playwright): `*.e2e.spec.ts`
### Best Practices
-* Test Behavior, Not Implementation. Don’t test internal state or function calls unless you’re testing utilities or very
+- Test Behavior, Not Implementation. Don’t test internal state or function calls unless you’re testing utilities or very
critical behavior.
-* Use msw to Mock APIs. Don't manually mock fetch—use msw to simulate realistic behavior, including network delays and
+- Use msw to Mock APIs. Don't manually mock fetch—use msw to simulate realistic behavior, including network delays and
errors.
-* Don’t Overuse Snapshots. Snapshots are fragile and often meaningless unless used sparingly (e.g., for JSON response
+- Don’t Overuse Snapshots. Snapshots are fragile and often meaningless unless used sparingly (e.g., for JSON response
schemas).
-* Prefer userEvent Over fireEvent. It simulates real user interactions more accurately.
-* Avoid Testing Next.js Internals . You don’t need to test getStaticProps, getServerSideProps themselves-test what they
+- Prefer userEvent Over fireEvent. It simulates real user interactions more accurately.
+- Avoid Testing Next.js Internals . You don’t need to test getStaticProps, getServerSideProps themselves-test what they
render.
-* Don't test just for coverage. Test to prevent regressions, document intent, and handle edge cases.
-* Don't write end-to-end tests for features that change frequently unless absolutely necessary.
+- Don't test just for coverage. Test to prevent regressions, document intent, and handle edge cases.
+- Don't write end-to-end tests for features that change frequently unless absolutely necessary.
### Jest Unit Testing Guide
@@ -675,14 +657,14 @@ yarn test path/to/test.unit.test.ts
#### Test Standards
- Test file names should convey what to expect
- - Follow the pattern: `.[unit,integration].test.ts`. Examples:
- - filename.unit.test.ts
- - filename.integration.test.ts
+ - Follow the pattern: `.[unit,integration].test.ts`. Examples:
+ - filename.unit.test.ts
+ - filename.integration.test.ts
- Group related tests using describe blocks
- Use descriptive test names that explain the expected behavior.
- - Follow the pattern: "should `expected behavior` [relevant modifier]". Examples:
- - should `ban user` [with matching user id]
- - should `ban user` [with matching user name]
+ - Follow the pattern: "should `expected behavior` [relevant modifier]". Examples:
+ - should `ban user` [with matching user id]
+ - should `ban user` [with matching user name]
#### Mocking
@@ -726,15 +708,15 @@ When writing mocks, assert both outcome and interaction:
Why mocking is important?
-- *Isolation* - Test your code independently of databases, APIs, and external systems. Tests only fail when your code
+- _Isolation_ - Test your code independently of databases, APIs, and external systems. Tests only fail when your code
breaks, not when a server is down.
-- *Speed* - Mocked tests run in milliseconds vs. seconds for real network/database calls. Run your suite constantly
+- _Speed_ - Mocked tests run in milliseconds vs. seconds for real network/database calls. Run your suite constantly
without waiting.
-- *Control* - Easily simulate edge cases like API errors, timeouts, or rare conditions that are difficult to reproduce
+- _Control_ - Easily simulate edge cases like API errors, timeouts, or rare conditions that are difficult to reproduce
with real systems.
-- *Reliability* - Eliminate unpredictable failures from network issues, rate limits, or changing external data. Same
+- _Reliability_ - Eliminate unpredictable failures from network issues, rate limits, or changing external data. Same
inputs = same results, every time.
-- *Focus* - Verify your function's logic and how it uses its dependencies, without requiring those dependencies to
+- _Focus_ - Verify your function's logic and how it uses its dependencies, without requiring those dependencies to
actually work yet.
###### Use `jest.mock()`
@@ -747,40 +729,40 @@ function’s return value isn’t used, there’s no need to mock it further.
```tsx
//Function and module mocks
-jest.mock('path/to/module');
+jest.mock('path/to/module')
//Function and module imports
-import {functionUnderTest} from "path/to/function"
-import {module} from "path/to/module"
+import {functionUnderTest} from 'path/to/function'
+import {module} from 'path/to/module'
describe('functionUnderTest', () => {
//Setup
beforeEach(() => {
//Run before each test
- jest.resetAllMocks(); // Resets any mocks from previous tests
- });
+ jest.resetAllMocks() // Resets any mocks from previous tests
+ })
afterEach(() => {
//Run after each test
- jest.restoreAllMocks(); // Cleans up between tests
- });
+ jest.restoreAllMocks() // Cleans up between tests
+ })
describe('when given valid input', () => {
it('should describe what is being tested', async () => {
//Arrange: Setup test data
- const mockData = 'test';
+ const mockData = 'test'
//Act: Execute the function under test
- const result = myFunction(mockData);
+ const result = myFunction(mockData)
//Assert: Verify the result
- expect(result).toBe('expected');
- });
- });
+ expect(result).toBe('expected')
+ })
+ })
describe('when an error occurs', () => {
//Test cases for errors
- });
-});
+ })
+})
```
###### Modules
@@ -790,27 +772,27 @@ called and what it was called with.
```tsx
//functionFile.ts
-import {module as mockedDep} from "path/to/module"
+import {module as mockedDep} from 'path/to/module'
export const functionUnderTest = async (param) => {
- return await mockedDep(param);
-};
+ return await mockedDep(param)
+}
```
```tsx
//testFile.unit.test.ts
-import {functionUnderTest} from "path/to/function";
-import {module as mockedDep} from "path/to/module";
+import {functionUnderTest} from 'path/to/function'
+import {module as mockedDep} from 'path/to/module'
-jest.mock('path/to/module');
+jest.mock('path/to/module')
/**
* Inside the test case
* We create a mock for any information passed into the function that is being tested
* and if the function returns a result we create a mock to test the result
*/
-const mockParam = "mockParam";
-const mockReturnValue = "mockModuleValue";
+const mockParam = 'mockParam'
+const mockReturnValue = 'mockModuleValue'
/**
* use .mockResolvedValue when handling async/await modules that return values
@@ -818,15 +800,15 @@ const mockReturnValue = "mockModuleValue";
*/
describe('functionUnderTest', () => {
it('returns mocked module value and calls dependency correctly', async () => {
- (mockedDep as jest.Mock).mockResolvedValue(mockReturnValue);
+ ;(mockedDep as jest.Mock).mockResolvedValue(mockReturnValue)
- const result = await functionUnderTest(mockParam);
+ const result = await functionUnderTest(mockParam)
- expect(result).toBe(mockReturnValue);
- expect(mockedDep).toHaveBeenCalledTimes(1);
- expect(mockedDep).toHaveBeenCalledWith(mockParam);
- });
-});
+ expect(result).toBe(mockReturnValue)
+ expect(mockedDep).toHaveBeenCalledTimes(1)
+ expect(mockedDep).toHaveBeenCalledWith(mockParam)
+ })
+})
```
Use namespace imports when you want to import everything a module exports under a single name.
@@ -834,37 +816,36 @@ Use namespace imports when you want to import everything a module exports under
```tsx
//moduleFile.ts
export const module = async (param) => {
- const value = "module"
+ const value = 'module'
return value
-};
+}
export const moduleTwo = async (param) => {
- const value = "moduleTwo"
+ const value = 'moduleTwo'
return value
-};
+}
```
```tsx
//functionFile.ts
-import {module, moduleTwo} from "path/to/module"
+import {module, moduleTwo} from 'path/to/module'
export const functionUnderTest = async (param) => {
const mockValue = await moduleTwo(param)
const returnValue = await module(mockValue)
- return returnValue;
-};
+ return returnValue
+}
```
```tsx
//testFile.unit.test.ts
-jest.mock('path/to/module');
+jest.mock('path/to/module')
/**
* This creates an object containing all named exports from ./path/to/module
*/
-import * as mockModule from "path/to/module"
-
-(mockModule.module as jest.Mock).mockResolvedValue(mockReturnValue);
+import * as mockModule from 'path/to/module'
+;(mockModule.module as jest.Mock).mockResolvedValue(mockReturnValue)
```
When mocking modules, you can use `jest.spyOn()` instead of `jest.mock()`.
@@ -872,21 +853,21 @@ When mocking modules, you can use `jest.spyOn()` instead of `jest.mock()`.
- `jest.mock()` mocks the entire module, which is ideal for external dependencies like Axios or database clients.
- `jest.spyOn()` mocks specific methods while keeping the real implementation for others. It can also be used to observe
how a real method is called without changing its behavior.
- - also replaces the need to have `jest.mock()` at the top of the file.
+ - also replaces the need to have `jest.mock()` at the top of the file.
```tsx
//testFile.unit.test.ts
-import * as mockModule from "path/to/module"
+import * as mockModule from 'path/to/module'
//Mocking the return value of the module
-jest.spyOn(mockModule, 'module').mockResolvedValue(mockReturnValue);
+jest.spyOn(mockModule, 'module').mockResolvedValue(mockReturnValue)
//Spying on the module to check functionality
-jest.spyOn(mockModule, 'module');
+jest.spyOn(mockModule, 'module')
//You can assert the module functionality with both of the above exactly like you would if you used jest.mock()
-expect(mockModule.module).toBeCalledTimes(1);
-expect(mockModule.module).toBeCalledWith(mockParam);
+expect(mockModule.module).toBeCalledTimes(1)
+expect(mockModule.module).toBeCalledWith(mockParam)
```
###### Dependencies
@@ -896,119 +877,114 @@ external functionality.
```tsx
//functionFile.ts
-import {dependency} from "path/to/dependency"
+import {dependency} from 'path/to/dependency'
export const functionUnderTest = async (param) => {
- const depen = await dependency();
- const value = depen.module();
+ const depen = await dependency()
+ const value = depen.module()
- return value;
-};
+ return value
+}
```
```tsx
//testFile.unit.test.ts
-jest.mock('path/to/dependency');
+jest.mock('path/to/dependency')
-import {dependency} from "path/to/dependency"
+import {dependency} from 'path/to/dependency'
describe('functionUnderTest', () => {
/**
* Because the dependency has modules that are used we need to
* create a variable outside of scope that can be asserted on
*/
- let mockDependency = {} as any;
+ let mockDependency = {} as any
beforeEach(() => {
mockDependency = {
module: jest.fn(),
- };
- jest.resetAllMocks(); // Resets any mocks from previous tests
- });
+ }
+ jest.resetAllMocks() // Resets any mocks from previous tests
+ })
afterEach(() => {
//Run after each test
- jest.restoreAllMocks(); // Cleans up between tests
- });
+ jest.restoreAllMocks() // Cleans up between tests
+ })
//Inside the test case
- (mockDependency.module as jest.Mock).mockResolvedValue(mockReturnValue);
+ ;(mockDependency.module as jest.Mock).mockResolvedValue(mockReturnValue)
- expect(mockDependency.module).toBeCalledTimes(1);
- expect(mockDependency.module).toBeCalledWith(mockParam);
-});
+ expect(mockDependency.module).toBeCalledTimes(1)
+ expect(mockDependency.module).toBeCalledWith(mockParam)
+})
```
###### Error checking
```tsx
//function.ts
-const result = await functionName(param);
+const result = await functionName(param)
if (!result) {
- throw new Error(403, 'Error text', error);
+ throw new Error(403, 'Error text', error)
}
-;
```
```tsx
//testFile.unit.test.ts
-const mockParam = {} as any;
+const mockParam = {} as any
//This will check only the error message
-expect(functionName(mockParam))
- .rejects
- .toThrowError('Error text');
+expect(functionName(mockParam)).rejects.toThrowError('Error text')
//This will check the complete error
try {
- await functionName(mockParam);
- fail('Should have thrown');
+ await functionName(mockParam)
+ fail('Should have thrown')
} catch (error) {
- const functionError = error as Error;
- expect(functionError.code).toBe(403);
- expect(functionError.message).toBe('Error text');
- expect(functionError.details).toBe(mockParam);
- expect(functionError.name).toBe('Error');
+ const functionError = error as Error
+ expect(functionError.code).toBe(403)
+ expect(functionError.message).toBe('Error text')
+ expect(functionError.details).toBe(mockParam)
+ expect(functionError.name).toBe('Error')
}
```
```tsx
//For console.error types
-console.error('Error message', error);
+console.error('Error message', error)
//Use spyOn to mock
-const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {
-});
+const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
expect(errorSpy).toHaveBeenCalledWith(
'Error message',
- expect.objectContaining({name: 'Error'}) //The error 'name' refers to the error type
-);
-
+ expect.objectContaining({name: 'Error'}), //The error 'name' refers to the error type
+)
```
###### Mocking array return value
```tsx
//arrayFile.ts
-const exampleArray = [1, 2, 3, 4, 5];
+const exampleArray = [1, 2, 3, 4, 5]
-const arrayResult = exampleArray.includes(2);
+const arrayResult = exampleArray.includes(2)
```
```tsx
//testFile.unit.test.ts
//This will mock 'includes' for all arrays and force the return value to be true
-jest.spyOn(Array.prototype, 'includes').mockReturnValue(true);
+jest.spyOn(Array.prototype, 'includes').mockReturnValue(true)
// ---
//This will specify which 'includes' array to mock based on the args passed into the .includes()
jest.spyOn(Array.prototype, 'includes').mockImplementation(function (value) {
if (value === 2) {
- return true;
+ return true
}
- return false;
-});
+ return false
+})
```
### Playwright (E2E) Testing Guide
@@ -1034,23 +1010,26 @@ yarn test:db:reset
Use this priority order for selecting elements in Playwright tests:
1. Prefer `getByRole()` — use semantic roles that reflect how users interact
+
```typescript
- await page.getByRole('button', { name: 'Submit' }).click();
+ await page.getByRole('button', {name: 'Submit'}).click()
```
+
If a meaningful ARIA role is not available, fall back to accessible text selectors (next point).
2. Use accessible text selectors — when roles don't apply, target user-facing text
+
```typescript
- await page.getByLabel('Email').fill('user@example.com');
- await page.getByPlaceholder('Enter your name').fill('John');
- await page.getByText('Welcome back').isVisible();
+ await page.getByLabel('Email').fill('user@example.com')
+ await page.getByPlaceholder('Enter your name').fill('John')
+ await page.getByText('Welcome back').isVisible()
```
3. Only use `data-testid` — when elements have no stable user-facing text
```typescript
// For icons, toggles, or dynamic content without text
- await page.getByTestId('menu-toggle').click();
- await page.getByTestId('loading-spinner').isVisible();
+ await page.getByTestId('menu-toggle').click()
+ await page.getByTestId('loading-spinner').isVisible()
```
This hierarchy mirrors how users actually interact with your application, making tests more reliable and meaningful.
diff --git a/.github/workflows/cd-android-live-update.yml b/.github/workflows/cd-android-live-update.yml
index 0cc3b209..8d0df364 100644
--- a/.github/workflows/cd-android-live-update.yml
+++ b/.github/workflows/cd-android-live-update.yml
@@ -1,10 +1,10 @@
name: CD Android Live Update
on:
push:
- branches: [ main, master ]
+ branches: [main, master]
paths:
- - "android/capawesome.json"
- - ".github/workflows/cd-android-live-update.yml"
+ - 'android/capawesome.json'
+ - '.github/workflows/cd-android-live-update.yml'
jobs:
deploy:
@@ -15,7 +15,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
with:
- fetch-depth: 0 # we need full history for git log
+ fetch-depth: 0 # we need full history for git log
- name: Install jq
run: sudo apt-get install -y jq
diff --git a/.github/workflows/cd-api.yml b/.github/workflows/cd-api.yml
index b975d49d..e05ab407 100644
--- a/.github/workflows/cd-api.yml
+++ b/.github/workflows/cd-api.yml
@@ -1,10 +1,10 @@
name: CD API
on:
push:
- branches: [ main, master ]
+ branches: [main, master]
paths:
- - "backend/api/package.json"
- - ".github/workflows/cd-api.yml"
+ - 'backend/api/package.json'
+ - '.github/workflows/cd-api.yml'
jobs:
deploy:
@@ -15,7 +15,7 @@ jobs:
- name: Checkout
uses: actions/checkout@v4
with:
- fetch-depth: 0 # we need full history for git log
+ fetch-depth: 0 # we need full history for git log
- name: Install jq
run: sudo apt-get install -y jq
diff --git a/.github/workflows/cd.yml b/.github/workflows/cd.yml
index a10f524a..85403916 100644
--- a/.github/workflows/cd.yml
+++ b/.github/workflows/cd.yml
@@ -2,13 +2,12 @@ name: CD
# Must select "Read and write permissions" in GitHub → Repo → Settings → Actions → General → Workflow permissions
-
on:
push:
- branches: [ main, master ]
+ branches: [main, master]
paths:
- - "package.json"
- - ".github/workflows/cd.yml"
+ - 'package.json'
+ - '.github/workflows/cd.yml'
jobs:
release:
@@ -18,7 +17,7 @@ jobs:
- name: Checkout repo
uses: actions/checkout@master
with:
- fetch-depth: 0 # To fetch all history for tags
+ fetch-depth: 0 # To fetch all history for tags
- name: Setup Node.js
uses: actions/setup-node@v4
@@ -32,4 +31,4 @@ jobs:
git config --global user.name "github-actions[bot]"
git config --global user.email "github-actions[bot]@users.noreply.github.com"
git remote set-url origin https://x-access-token:${{ secrets.GITHUB_TOKEN }}@github.com/${{ github.repository }}
- ./scripts/release.sh
\ No newline at end of file
+ ./scripts/release.sh
diff --git a/.github/workflows/ci-e2e.yml b/.github/workflows/ci-e2e.yml
index 2ef53776..1bb783f6 100644
--- a/.github/workflows/ci-e2e.yml
+++ b/.github/workflows/ci-e2e.yml
@@ -2,9 +2,9 @@ name: E2E Tests
on:
push:
- branches: [ main ]
+ branches: [main]
pull_request:
- branches: [ main ]
+ branches: [main]
jobs:
e2e:
@@ -26,7 +26,7 @@ jobs:
uses: actions/setup-java@v4
with:
distribution: 'temurin'
- java-version: '21' # Required for firebase-tools@15+
+ java-version: '21' # Required for firebase-tools@15+
- name: Setup Supabase CLI
uses: supabase/setup-cli@v1
@@ -41,8 +41,8 @@ jobs:
- name: Run E2E tests
env:
- SKIP_DB_CLEANUP: true # Don't try to stop Docker in CI
- FIREBASE_TOKEN: "dummy" # Suppresses auth warning
+ SKIP_DB_CLEANUP: true # Don't try to stop Docker in CI
+ FIREBASE_TOKEN: 'dummy' # Suppresses auth warning
# or
run: |
yarn test:e2e
@@ -61,4 +61,4 @@ jobs:
with:
name: test-results
path: test-results/
- retention-days: 7
\ No newline at end of file
+ retention-days: 7
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 18631f48..79d38322 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -2,9 +2,9 @@ name: Jest Tests
on:
push:
- branches: [ main ]
+ branches: [main]
pull_request:
- branches: [ main ]
+ branches: [main]
jobs:
ci:
@@ -29,22 +29,22 @@ jobs:
run: yarn lint
- name: Type check
- run: npx tsc --noEmit
+ run: yarn typecheck
- name: Run Jest tests
env:
NEXT_PUBLIC_FIREBASE_ENV: DEV
run: |
yarn test:coverage
-# npm install -g lcov-result-merger
-# mkdir coverage
-# lcov-result-merger \
-# "backend/api/coverage/lcov.info" \
-# "backend/shared/coverage/lcov.info" \
-# "backend/email/coverage/lcov.info" \
-# "common/coverage/lcov.info" \
-# "web/coverage/lcov.info" \
-# > coverage/lcov.info
+ # npm install -g lcov-result-merger
+ # mkdir coverage
+ # lcov-result-merger \
+ # "backend/api/coverage/lcov.info" \
+ # "backend/shared/coverage/lcov.info" \
+ # "backend/email/coverage/lcov.info" \
+ # "common/coverage/lcov.info" \
+ # "web/coverage/lcov.info" \
+ # > coverage/lcov.info
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v5
diff --git a/.junie/guidelines.md b/.junie/guidelines.md
index 91c504e9..e6f75f32 100644
--- a/.junie/guidelines.md
+++ b/.junie/guidelines.md
@@ -1,23 +1,23 @@
---
trigger: always_on
description:
-globs:
+globs:
---
## Project Structure
- next.js react tailwind frontend `/web`
- - broken down into pages, components, hooks, lib
+ - broken down into pages, components, hooks, lib
- express node api server `/backend/api`
- one off scripts, like migrations `/backend/scripts`
- supabase postgres. schema in `/backend/supabase`
- - supabase-generated types in `/backend/supabase/schema.ts`
+ - supabase-generated types in `/backend/supabase/schema.ts`
- files shared between backend directories `/backend/shared`
- - anything in `/backend` can import from `shared`, but not vice versa
+ - anything in `/backend` can import from `shared`, but not vice versa
- files shared between the frontend and backend in `/common`
- - `/common` has lots of type definitions for our data structures, like User. It also contains many useful utility
- functions. We try not to add package dependencies to common. `/web` and `/backend` are allowed to import from
- `/common`, but not vice versa.
+ - `/common` has lots of type definitions for our data structures, like User. It also contains many useful utility
+ functions. We try not to add package dependencies to common. `/web` and `/backend` are allowed to import from
+ `/common`, but not vice versa.
## Deployment
@@ -52,18 +52,11 @@ export function HeadlineTabs(props: {
notSticky?: boolean
className?: string
}) {
- const {headlines, endpoint, currentSlug, hideEmoji, notSticky, className} =
- props
+ const {headlines, endpoint, currentSlug, hideEmoji, notSticky, className} = props
const user = useUser()
return (
-
+
{headlines.map(({id, slug, title}) => (
))}
- {user && }
+ {user && }
{user && (isAdminId(user.id) || isModId(user.id)) && (
-
+
)}
@@ -146,9 +139,7 @@ Here's the definition of usePersistentInMemoryState:
```ts
export const usePersistentInMemoryState =
(initialValue: T, key: string) => {
- const [state, setState] = useStateCheckEquality(
- safeJsonParse(store[key]) ?? initialValue
- )
+ const [state, setState] = useStateCheckEquality(safeJsonParse(store[key]) ?? initialValue)
useEffect(() => {
const storedValue = safeJsonParse(store[key]) ?? initialValue
@@ -192,7 +183,7 @@ In `use-bets`, we have this hook to get live updates with useApiSubscription:
```ts
export const useContractBets = (
contractId: string,
- opts?: APIParams<'bets'> & { enabled?: boolean }
+ opts?: APIParams<'bets'> & {enabled?: boolean},
) => {
const {enabled = true, ...apiOptions} = {
contractId,
@@ -200,17 +191,11 @@ export const useContractBets = (
}
const optionsKey = JSON.stringify(apiOptions)
- const [newBets, setNewBets] = usePersistentInMemoryState(
- [],
- `${optionsKey}-bets`
- )
+ const [newBets, setNewBets] = usePersistentInMemoryState([], `${optionsKey}-bets`)
const addBets = (bets: Bet[]) => {
setNewBets((currentBets) => {
- const uniqueBets = sortBy(
- uniqBy([...currentBets, ...bets], 'id'),
- 'createdTime'
- )
+ const uniqueBets = sortBy(uniqBy([...currentBets, ...bets], 'id'), 'createdTime')
return uniqueBets.filter((b) => !betShouldBeFiltered(b, apiOptions))
})
}
@@ -245,7 +230,7 @@ export function broadcastUpdatedPrivateUser(userId: string) {
broadcast(`private-user/${userId}`, {})
}
-export function broadcastUpdatedUser(user: Partial & { id: string }) {
+export function broadcastUpdatedUser(user: Partial & {id: string}) {
broadcast(`user/${user.id}`, {user})
}
@@ -330,7 +315,7 @@ export const placeBet: APIHandler<'bet'> = async (props, auth) => {
const isApi = auth.creds.kind === 'key'
return await betsQueue.enqueueFn(
() => placeBetMain(props, auth.uid, isApi),
- [props.contractId, auth.uid]
+ [props.contractId, auth.uid],
)
}
```
@@ -376,13 +361,10 @@ using the pg-promise library. The client (code in web) does not have permission
Another example using the direct client:
```ts
-export const getUniqueBettorIds = async (
- contractId: string,
- pg: SupabaseDirectClient
-) => {
+export const getUniqueBettorIds = async (contractId: string, pg: SupabaseDirectClient) => {
const res = await pg.manyOrNone(
'select distinct user_id from contract_bets where contract_id = $1',
- [contractId]
+ [contractId],
)
return res.map((r) => r.user_id as string)
}
@@ -437,7 +419,7 @@ const query = renderSql(
from('contract_bets'),
where('contract_id = ${id}', {id}),
orderBy('created_time desc'),
- limitValue != null && limit(limitValue)
+ limitValue != null && limit(limitValue),
)
const res = await pg.manyOrNone(query)
@@ -476,7 +458,7 @@ can do so via this SQL command (change the type if not `TEXT`):
```sql
ALTER TABLE profiles
- ADD COLUMN profile_field TEXT;
+ADD COLUMN profile_field TEXT;
```
Store it in `add_profile_field.sql` in the [migrations](../backend/supabase/migrations) folder and
@@ -540,28 +522,28 @@ How we apply it here
This project uses three complementary test types. Use the right level for the job:
- Unit tests
- - Purpose: Verify a single function/module in isolation; fast, deterministic.
- - Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
- etc.).
- - Runner: Jest (configured via root `jest.config.js`).
- - Naming: `*.unit.test.ts` (or `.tsx` for React in `web`).
- - When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
+ - Purpose: Verify a single function/module in isolation; fast, deterministic.
+ - Where: Each package under `tests/unit` (e.g., `backend/api/tests/unit`, `web/tests/unit`, `common/tests/unit`,
+ etc.).
+ - Runner: Jest (configured via root `jest.config.js`).
+ - Naming: `*.unit.test.ts` (or `.tsx` for React in `web`).
+ - When to use: Pure logic, utilities, hooks, reducers, small components with mocked dependencies.
- Integration tests
- - Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
- spinning up the full app.
- - Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).
- - Runner: Jest (configured via root `jest.config.js`).
- - Naming: `*.integration.test.ts` (or `.tsx` for React in `web`).
- - When to use: Boundaries between modules, real serialization/parsing, API handlers with mocked network/DB,
- component trees with providers.
+ - Purpose: Verify multiple units working together (e.g., function + DB/client, component + context/provider) without
+ spinning up the full app.
+ - Where: Each package under `tests/integration` (e.g., `backend/shared/tests/integration`, `web/tests/integration`).
+ - Runner: Jest (configured via root `jest.config.js`).
+ - Naming: `*.integration.test.ts` (or `.tsx` for React in `web`).
+ - When to use: Boundaries between modules, real serialization/parsing, API handlers with mocked network/DB,
+ component trees with providers.
- End-to-End (E2E) tests
- - Purpose: Validate real user flows across the full stack.
- - Where: Top-level `tests/e2e` with separate areas for `web` and `backend`.
- - Runner: Playwright (see root `playwright.config.ts`, `testDir: ./tests/e2e`).
- - Naming: `*.e2e.spec.ts`.
- - When to use: Critical journeys (signup, login, checkout), cross-service interactions, smoke tests for deployments.
+ - Purpose: Validate real user flows across the full stack.
+ - Where: Top-level `tests/e2e` with separate areas for `web` and `backend`.
+ - Runner: Playwright (see root `playwright.config.ts`, `testDir: ./tests/e2e`).
+ - Naming: `*.e2e.spec.ts`.
+ - When to use: Critical journeys (signup, login, checkout), cross-service interactions, smoke tests for deployments.
Quick commands
@@ -631,23 +613,23 @@ web/
- Unit and integration tests live in each package’s `tests` folder and are executed by Jest via the root
`jest.config.js` projects array.
- Naming:
- - Unit: `*.unit.test.ts` (or `.tsx` for React in `web`)
- - Integration: `*.integration.test.ts`
- - E2E (Playwright): `*.e2e.spec.ts`
+ - Unit: `*.unit.test.ts` (or `.tsx` for React in `web`)
+ - Integration: `*.integration.test.ts`
+ - E2E (Playwright): `*.e2e.spec.ts`
### Best Practices
-* Test Behavior, Not Implementation. Don’t test internal state or function calls unless you’re testing utilities or very
+- Test Behavior, Not Implementation. Don’t test internal state or function calls unless you’re testing utilities or very
critical behavior.
-* Use msw to Mock APIs. Don't manually mock fetch—use msw to simulate realistic behavior, including network delays and
+- Use msw to Mock APIs. Don't manually mock fetch—use msw to simulate realistic behavior, including network delays and
errors.
-* Don’t Overuse Snapshots. Snapshots are fragile and often meaningless unless used sparingly (e.g., for JSON response
+- Don’t Overuse Snapshots. Snapshots are fragile and often meaningless unless used sparingly (e.g., for JSON response
schemas).
-* Prefer userEvent Over fireEvent. It simulates real user interactions more accurately.
-* Avoid Testing Next.js Internals . You don’t need to test getStaticProps, getServerSideProps themselves-test what they
+- Prefer userEvent Over fireEvent. It simulates real user interactions more accurately.
+- Avoid Testing Next.js Internals . You don’t need to test getStaticProps, getServerSideProps themselves-test what they
render.
-* Don't test just for coverage. Test to prevent regressions, document intent, and handle edge cases.
-* Don't write end-to-end tests for features that change frequently unless absolutely necessary.
+- Don't test just for coverage. Test to prevent regressions, document intent, and handle edge cases.
+- Don't write end-to-end tests for features that change frequently unless absolutely necessary.
### Jest Unit Testing Guide
@@ -675,14 +657,14 @@ yarn test path/to/test.unit.test.ts
#### Test Standards
- Test file names should convey what to expect
- - Follow the pattern: `.[unit,integration].test.ts`. Examples:
- - filename.unit.test.ts
- - filename.integration.test.ts
+ - Follow the pattern: `.[unit,integration].test.ts`. Examples:
+ - filename.unit.test.ts
+ - filename.integration.test.ts
- Group related tests using describe blocks
- Use descriptive test names that explain the expected behavior.
- - Follow the pattern: "should `expected behavior` [relevant modifier]". Examples:
- - should `ban user` [with matching user id]
- - should `ban user` [with matching user name]
+ - Follow the pattern: "should `expected behavior` [relevant modifier]". Examples:
+ - should `ban user` [with matching user id]
+ - should `ban user` [with matching user name]
#### Mocking
@@ -726,15 +708,15 @@ When writing mocks, assert both outcome and interaction:
Why mocking is important?
-- *Isolation* - Test your code independently of databases, APIs, and external systems. Tests only fail when your code
+- _Isolation_ - Test your code independently of databases, APIs, and external systems. Tests only fail when your code
breaks, not when a server is down.
-- *Speed* - Mocked tests run in milliseconds vs. seconds for real network/database calls. Run your suite constantly
+- _Speed_ - Mocked tests run in milliseconds vs. seconds for real network/database calls. Run your suite constantly
without waiting.
-- *Control* - Easily simulate edge cases like API errors, timeouts, or rare conditions that are difficult to reproduce
+- _Control_ - Easily simulate edge cases like API errors, timeouts, or rare conditions that are difficult to reproduce
with real systems.
-- *Reliability* - Eliminate unpredictable failures from network issues, rate limits, or changing external data. Same
+- _Reliability_ - Eliminate unpredictable failures from network issues, rate limits, or changing external data. Same
inputs = same results, every time.
-- *Focus* - Verify your function's logic and how it uses its dependencies, without requiring those dependencies to
+- _Focus_ - Verify your function's logic and how it uses its dependencies, without requiring those dependencies to
actually work yet.
###### Use `jest.mock()`
@@ -747,40 +729,40 @@ function’s return value isn’t used, there’s no need to mock it further.
```tsx
//Function and module mocks
-jest.mock('path/to/module');
+jest.mock('path/to/module')
//Function and module imports
-import {functionUnderTest} from "path/to/function"
-import {module} from "path/to/module"
+import {functionUnderTest} from 'path/to/function'
+import {module} from 'path/to/module'
describe('functionUnderTest', () => {
//Setup
beforeEach(() => {
//Run before each test
- jest.resetAllMocks(); // Resets any mocks from previous tests
- });
+ jest.resetAllMocks() // Resets any mocks from previous tests
+ })
afterEach(() => {
//Run after each test
- jest.restoreAllMocks(); // Cleans up between tests
- });
+ jest.restoreAllMocks() // Cleans up between tests
+ })
describe('when given valid input', () => {
it('should describe what is being tested', async () => {
//Arrange: Setup test data
- const mockData = 'test';
+ const mockData = 'test'
//Act: Execute the function under test
- const result = myFunction(mockData);
+ const result = myFunction(mockData)
//Assert: Verify the result
- expect(result).toBe('expected');
- });
- });
+ expect(result).toBe('expected')
+ })
+ })
describe('when an error occurs', () => {
//Test cases for errors
- });
-});
+ })
+})
```
###### Modules
@@ -790,27 +772,27 @@ called and what it was called with.
```tsx
//functionFile.ts
-import {module as mockedDep} from "path/to/module"
+import {module as mockedDep} from 'path/to/module'
export const functionUnderTest = async (param) => {
- return await mockedDep(param);
-};
+ return await mockedDep(param)
+}
```
```tsx
//testFile.unit.test.ts
-import {functionUnderTest} from "path/to/function";
-import {module as mockedDep} from "path/to/module";
+import {functionUnderTest} from 'path/to/function'
+import {module as mockedDep} from 'path/to/module'
-jest.mock('path/to/module');
+jest.mock('path/to/module')
/**
* Inside the test case
* We create a mock for any information passed into the function that is being tested
* and if the function returns a result we create a mock to test the result
*/
-const mockParam = "mockParam";
-const mockReturnValue = "mockModuleValue";
+const mockParam = 'mockParam'
+const mockReturnValue = 'mockModuleValue'
/**
* use .mockResolvedValue when handling async/await modules that return values
@@ -818,15 +800,15 @@ const mockReturnValue = "mockModuleValue";
*/
describe('functionUnderTest', () => {
it('returns mocked module value and calls dependency correctly', async () => {
- (mockedDep as jest.Mock).mockResolvedValue(mockReturnValue);
+ ;(mockedDep as jest.Mock).mockResolvedValue(mockReturnValue)
- const result = await functionUnderTest(mockParam);
+ const result = await functionUnderTest(mockParam)
- expect(result).toBe(mockReturnValue);
- expect(mockedDep).toHaveBeenCalledTimes(1);
- expect(mockedDep).toHaveBeenCalledWith(mockParam);
- });
-});
+ expect(result).toBe(mockReturnValue)
+ expect(mockedDep).toHaveBeenCalledTimes(1)
+ expect(mockedDep).toHaveBeenCalledWith(mockParam)
+ })
+})
```
Use namespace imports when you want to import everything a module exports under a single name.
@@ -834,37 +816,36 @@ Use namespace imports when you want to import everything a module exports under
```tsx
//moduleFile.ts
export const module = async (param) => {
- const value = "module"
+ const value = 'module'
return value
-};
+}
export const moduleTwo = async (param) => {
- const value = "moduleTwo"
+ const value = 'moduleTwo'
return value
-};
+}
```
```tsx
//functionFile.ts
-import {module, moduleTwo} from "path/to/module"
+import {module, moduleTwo} from 'path/to/module'
export const functionUnderTest = async (param) => {
const mockValue = await moduleTwo(param)
const returnValue = await module(mockValue)
- return returnValue;
-};
+ return returnValue
+}
```
```tsx
//testFile.unit.test.ts
-jest.mock('path/to/module');
+jest.mock('path/to/module')
/**
* This creates an object containing all named exports from ./path/to/module
*/
-import * as mockModule from "path/to/module"
-
-(mockModule.module as jest.Mock).mockResolvedValue(mockReturnValue);
+import * as mockModule from 'path/to/module'
+;(mockModule.module as jest.Mock).mockResolvedValue(mockReturnValue)
```
When mocking modules, you can use `jest.spyOn()` instead of `jest.mock()`.
@@ -872,21 +853,21 @@ When mocking modules, you can use `jest.spyOn()` instead of `jest.mock()`.
- `jest.mock()` mocks the entire module, which is ideal for external dependencies like Axios or database clients.
- `jest.spyOn()` mocks specific methods while keeping the real implementation for others. It can also be used to observe
how a real method is called without changing its behavior.
- - also replaces the need to have `jest.mock()` at the top of the file.
+ - also replaces the need to have `jest.mock()` at the top of the file.
```tsx
//testFile.unit.test.ts
-import * as mockModule from "path/to/module"
+import * as mockModule from 'path/to/module'
//Mocking the return value of the module
-jest.spyOn(mockModule, 'module').mockResolvedValue(mockReturnValue);
+jest.spyOn(mockModule, 'module').mockResolvedValue(mockReturnValue)
//Spying on the module to check functionality
-jest.spyOn(mockModule, 'module');
+jest.spyOn(mockModule, 'module')
//You can assert the module functionality with both of the above exactly like you would if you used jest.mock()
-expect(mockModule.module).toBeCalledTimes(1);
-expect(mockModule.module).toBeCalledWith(mockParam);
+expect(mockModule.module).toBeCalledTimes(1)
+expect(mockModule.module).toBeCalledWith(mockParam)
```
###### Dependencies
@@ -896,119 +877,114 @@ external functionality.
```tsx
//functionFile.ts
-import {dependency} from "path/to/dependency"
+import {dependency} from 'path/to/dependency'
export const functionUnderTest = async (param) => {
- const depen = await dependency();
- const value = depen.module();
+ const depen = await dependency()
+ const value = depen.module()
- return value;
-};
+ return value
+}
```
```tsx
//testFile.unit.test.ts
-jest.mock('path/to/dependency');
+jest.mock('path/to/dependency')
-import {dependency} from "path/to/dependency"
+import {dependency} from 'path/to/dependency'
describe('functionUnderTest', () => {
/**
* Because the dependency has modules that are used we need to
* create a variable outside of scope that can be asserted on
*/
- let mockDependency = {} as any;
+ let mockDependency = {} as any
beforeEach(() => {
mockDependency = {
module: jest.fn(),
- };
- jest.resetAllMocks(); // Resets any mocks from previous tests
- });
+ }
+ jest.resetAllMocks() // Resets any mocks from previous tests
+ })
afterEach(() => {
//Run after each test
- jest.restoreAllMocks(); // Cleans up between tests
- });
+ jest.restoreAllMocks() // Cleans up between tests
+ })
//Inside the test case
- (mockDependency.module as jest.Mock).mockResolvedValue(mockReturnValue);
+ ;(mockDependency.module as jest.Mock).mockResolvedValue(mockReturnValue)
- expect(mockDependency.module).toBeCalledTimes(1);
- expect(mockDependency.module).toBeCalledWith(mockParam);
-});
+ expect(mockDependency.module).toBeCalledTimes(1)
+ expect(mockDependency.module).toBeCalledWith(mockParam)
+})
```
###### Error checking
```tsx
//function.ts
-const result = await functionName(param);
+const result = await functionName(param)
if (!result) {
- throw new Error(403, 'Error text', error);
+ throw new Error(403, 'Error text', error)
}
-;
```
```tsx
//testFile.unit.test.ts
-const mockParam = {} as any;
+const mockParam = {} as any
//This will check only the error message
-expect(functionName(mockParam))
- .rejects
- .toThrowError('Error text');
+expect(functionName(mockParam)).rejects.toThrowError('Error text')
//This will check the complete error
try {
- await functionName(mockParam);
- fail('Should have thrown');
+ await functionName(mockParam)
+ fail('Should have thrown')
} catch (error) {
- const functionError = error as Error;
- expect(functionError.code).toBe(403);
- expect(functionError.message).toBe('Error text');
- expect(functionError.details).toBe(mockParam);
- expect(functionError.name).toBe('Error');
+ const functionError = error as Error
+ expect(functionError.code).toBe(403)
+ expect(functionError.message).toBe('Error text')
+ expect(functionError.details).toBe(mockParam)
+ expect(functionError.name).toBe('Error')
}
```
```tsx
//For console.error types
-console.error('Error message', error);
+console.error('Error message', error)
//Use spyOn to mock
-const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {
-});
+const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
expect(errorSpy).toHaveBeenCalledWith(
'Error message',
- expect.objectContaining({name: 'Error'}) //The error 'name' refers to the error type
-);
-
+ expect.objectContaining({name: 'Error'}), //The error 'name' refers to the error type
+)
```
###### Mocking array return value
```tsx
//arrayFile.ts
-const exampleArray = [1, 2, 3, 4, 5];
+const exampleArray = [1, 2, 3, 4, 5]
-const arrayResult = exampleArray.includes(2);
+const arrayResult = exampleArray.includes(2)
```
```tsx
//testFile.unit.test.ts
//This will mock 'includes' for all arrays and force the return value to be true
-jest.spyOn(Array.prototype, 'includes').mockReturnValue(true);
+jest.spyOn(Array.prototype, 'includes').mockReturnValue(true)
// ---
//This will specify which 'includes' array to mock based on the args passed into the .includes()
jest.spyOn(Array.prototype, 'includes').mockImplementation(function (value) {
if (value === 2) {
- return true;
+ return true
}
- return false;
-});
+ return false
+})
```
### Playwright (E2E) Testing Guide
@@ -1034,23 +1010,26 @@ yarn test:db:reset
Use this priority order for selecting elements in Playwright tests:
1. Prefer `getByRole()` — use semantic roles that reflect how users interact
+
```typescript
- await page.getByRole('button', { name: 'Submit' }).click();
+ await page.getByRole('button', {name: 'Submit'}).click()
```
+
If a meaningful ARIA role is not available, fall back to accessible text selectors (next point).
2. Use accessible text selectors — when roles don't apply, target user-facing text
+
```typescript
- await page.getByLabel('Email').fill('user@example.com');
- await page.getByPlaceholder('Enter your name').fill('John');
- await page.getByText('Welcome back').isVisible();
+ await page.getByLabel('Email').fill('user@example.com')
+ await page.getByPlaceholder('Enter your name').fill('John')
+ await page.getByText('Welcome back').isVisible()
```
3. Only use `data-testid` — when elements have no stable user-facing text
```typescript
// For icons, toggles, or dynamic content without text
- await page.getByTestId('menu-toggle').click();
- await page.getByTestId('loading-spinner').isVisible();
+ await page.getByTestId('menu-toggle').click()
+ await page.getByTestId('loading-spinner').isVisible()
```
This hierarchy mirrors how users actually interact with your application, making tests more reliable and meaningful.
diff --git a/.prettierignore b/.prettierignore
new file mode 100644
index 00000000..ad83ab78
--- /dev/null
+++ b/.prettierignore
@@ -0,0 +1,35 @@
+# Dependencies
+node_modules
+.yarn
+
+# Build outputs
+dist
+build
+.next
+out
+lib
+
+# Generated files
+coverage
+*.min.js
+*.min.css
+
+# Database / migrations
+**/*.sql
+
+# Config / lock files
+yarn.lock
+package-lock.json
+pnpm-lock.yaml
+
+# Android / iOS
+android
+ios
+capacitor.config.ts
+
+# Playwright
+tests/reports
+playwright-report
+
+coverage
+.vscode
\ No newline at end of file
diff --git a/.prettierrc b/.prettierrc
index 8f448141..cfbc2803 100644
--- a/.prettierrc
+++ b/.prettierrc
@@ -2,9 +2,11 @@
"tabWidth": 2,
"useTabs": false,
"semi": false,
- "trailingComma": "es5",
"singleQuote": true,
+ "singleAttributePerLine": false,
"bracketSpacing": false,
+ "printWidth": 100,
+ "trailingComma": "all",
"plugins": ["prettier-plugin-sql"],
"overrides": [
{
diff --git a/.windsurf/rules/compass.md b/.windsurf/rules/compass.md
index a0e5318b..154ffc55 100644
--- a/.windsurf/rules/compass.md
+++ b/.windsurf/rules/compass.md
@@ -1,7 +1,7 @@
---
trigger: always_on
-description:
-globs:
+description:
+globs:
---
## Project Structure
@@ -33,14 +33,14 @@ Here's an example component from web in our style:
import clsx from 'clsx'
import Link from 'next/link'
-import { isAdminId, isModId } from 'common/envs/constants'
-import { type Headline } from 'common/news'
-import { EditNewsButton } from 'web/components/news/edit-news-button'
-import { Carousel } from 'web/components/widgets/carousel'
-import { useUser } from 'web/hooks/use-user'
-import { track } from 'web/lib/service/analytics'
-import { DashboardEndpoints } from 'web/components/dashboard/dashboard-page'
-import { removeEmojis } from 'common/util/string'
+import {isAdminId, isModId} from 'common/envs/constants'
+import {type Headline} from 'common/news'
+import {EditNewsButton} from 'web/components/news/edit-news-button'
+import {Carousel} from 'web/components/widgets/carousel'
+import {useUser} from 'web/hooks/use-user'
+import {track} from 'web/lib/service/analytics'
+import {DashboardEndpoints} from 'web/components/dashboard/dashboard-page'
+import {removeEmojis} from 'common/util/string'
export function HeadlineTabs(props: {
headlines: Headline[]
@@ -50,20 +50,13 @@ export function HeadlineTabs(props: {
notSticky?: boolean
className?: string
}) {
- const { headlines, endpoint, currentSlug, hideEmoji, notSticky, className } =
- props
+ const {headlines, endpoint, currentSlug, hideEmoji, notSticky, className} = props
const user = useUser()
return (
-
+
- {headlines.map(({ id, slug, title }) => (
+ {headlines.map(({id, slug, title}) => (
(initialValue: T, key: string) => {
- const [state, setState] = useStateCheckEquality(
- safeJsonParse(store[key]) ?? initialValue
- )
+ const [state, setState] = useStateCheckEquality(safeJsonParse(store[key]) ?? initialValue)
useEffect(() => {
const storedValue = safeJsonParse(store[key]) ?? initialValue
@@ -183,25 +174,19 @@ In `use-bets`, we have this hook to get live updates with useApiSubscription:
```ts
export const useContractBets = (
contractId: string,
- opts?: APIParams<'bets'> & { enabled?: boolean }
+ opts?: APIParams<'bets'> & {enabled?: boolean},
) => {
- const { enabled = true, ...apiOptions } = {
+ const {enabled = true, ...apiOptions} = {
contractId,
...opts,
}
const optionsKey = JSON.stringify(apiOptions)
- const [newBets, setNewBets] = usePersistentInMemoryState(
- [],
- `${optionsKey}-bets`
- )
+ const [newBets, setNewBets] = usePersistentInMemoryState([], `${optionsKey}-bets`)
const addBets = (bets: Bet[]) => {
setNewBets((currentBets) => {
- const uniqueBets = sortBy(
- uniqBy([...currentBets, ...bets], 'id'),
- 'createdTime'
- )
+ const uniqueBets = sortBy(uniqBy([...currentBets, ...bets], 'id'), 'createdTime')
return uniqueBets.filter((b) => !betShouldBeFiltered(b, apiOptions))
})
}
@@ -236,12 +221,12 @@ export function broadcastUpdatedPrivateUser(userId: string) {
broadcast(`private-user/${userId}`, {})
}
-export function broadcastUpdatedUser(user: Partial & { id: string }) {
- broadcast(`user/${user.id}`, { user })
+export function broadcastUpdatedUser(user: Partial & {id: string}) {
+ broadcast(`user/${user.id}`, {user})
}
export function broadcastUpdatedComment(comment: Comment) {
- broadcast(`user/${comment.onUserId}/comment`, { comment })
+ broadcast(`user/${comment.onUserId}/comment`, {comment})
}
```
@@ -310,7 +295,7 @@ export const placeBet: APIHandler<'bet'> = async (props, auth) => {
const isApi = auth.creds.kind === 'key'
return await betsQueue.enqueueFn(
() => placeBetMain(props, auth.uid, isApi),
- [props.contractId, auth.uid]
+ [props.contractId, auth.uid],
)
}
```
@@ -332,7 +317,7 @@ const handlers = {
We have two ways to access our postgres database.
```ts
-import { db } from 'web/lib/supabase/db'
+import {db} from 'web/lib/supabase/db'
db.from('profiles').select('*').eq('user_id', userId)
```
@@ -340,7 +325,7 @@ db.from('profiles').select('*').eq('user_id', userId)
and
```ts
-import { createSupabaseDirectClient } from 'shared/supabase/init'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
const pg = createSupabaseDirectClient()
pg.oneOrNone>('select * from profiles where user_id = $1', [userId])
@@ -353,13 +338,10 @@ The supabase client just uses the supabase client library, which is a wrapper ar
Another example using the direct client:
```ts
-export const getUniqueBettorIds = async (
- contractId: string,
- pg: SupabaseDirectClient
-) => {
+export const getUniqueBettorIds = async (contractId: string, pg: SupabaseDirectClient) => {
const res = await pg.manyOrNone(
'select distinct user_id from contract_bets where contract_id = $1',
- [contractId]
+ [contractId],
)
return res.map((r) => r.user_id as string)
}
@@ -411,12 +393,12 @@ Example usage:
const query = renderSql(
select('distinct user_id'),
from('contract_bets'),
- where('contract_id = ${id}', { id }),
+ where('contract_id = ${id}', {id}),
orderBy('created_time desc'),
- limitValue != null && limit(limitValue)
+ limitValue != null && limit(limitValue),
)
const res = await pg.manyOrNone(query)
```
-Use these functions instead of string concatenation.
\ No newline at end of file
+Use these functions instead of string concatenation.
diff --git a/.windsurf/rules/next.md b/.windsurf/rules/next.md
index fa377ded..66bd0fd4 100644
--- a/.windsurf/rules/next.md
+++ b/.windsurf/rules/next.md
@@ -1,16 +1,16 @@
---
trigger: manual
description:
-globs:
+globs:
---
### Translations
```typescript
-import {useT} from "web/lib/locale";
+import {useT} from 'web/lib/locale'
const t = useT()
-t("common.key", "English translations")
+t('common.key', 'English translations')
```
Translations should go to the JSON files in `web/messages` (`de.json` and `fr.json`, as of now).
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
index 10ae4644..177f11ac 100644
--- a/CODE_OF_CONDUCT.md
+++ b/CODE_OF_CONDUCT.md
@@ -17,23 +17,23 @@ diverse, inclusive, and healthy community.
Examples of behavior that contributes to a positive environment for our
community include:
-* Demonstrating empathy and kindness toward other people
-* Being respectful of differing opinions, viewpoints, and experiences
-* Giving and gracefully accepting constructive feedback
-* Accepting responsibility and apologizing to those affected by our mistakes,
+- Demonstrating empathy and kindness toward other people
+- Being respectful of differing opinions, viewpoints, and experiences
+- Giving and gracefully accepting constructive feedback
+- Accepting responsibility and apologizing to those affected by our mistakes,
and learning from the experience
-* Focusing on what is best not just for us as individuals, but for the
+- Focusing on what is best not just for us as individuals, but for the
overall community
Examples of unacceptable behavior include:
-* The use of sexualized language or imagery, and sexual attention or
+- The use of sexualized language or imagery, and sexual attention or
advances of any kind
-* Trolling, insulting or derogatory comments, and personal or political attacks
-* Public or private harassment
-* Publishing others' private information, such as a physical or email
+- Trolling, insulting or derogatory comments, and personal or political attacks
+- Public or private harassment
+- Publishing others' private information, such as a physical or email
address, without their explicit permission
-* Other conduct which could reasonably be considered inappropriate in a
+- Other conduct which could reasonably be considered inappropriate in a
professional setting
## Enforcement Responsibilities
@@ -106,7 +106,7 @@ Violating these terms may lead to a permanent ban.
### 4. Permanent Ban
**Community Impact**: Demonstrating a pattern of violation of community
-standards, including sustained inappropriate behavior, harassment of an
+standards, including sustained inappropriate behavior, harassment of an
individual, or aggression toward or disparagement of classes of individuals.
**Consequence**: A permanent ban from any sort of public interaction within
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index bbb46b0b..b33229d7 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -14,10 +14,13 @@ We welcome pull requests, but only if they meet the project's quality and design
1. **Fork the repository** using the GitHub UI.
2. **Clone your fork** locally:
+
```bash
git clone https://github.com/your-username/Compass.git
cd your-fork
+ ```
+
3. **Add the upstream remote**:
```bash
@@ -95,27 +98,26 @@ Or whatever command is defined in the repo.
When opening a pull request:
-* **Title**: Describe what the PR does, clearly and specifically.
-* **Description**: Explain the context. Link related issues (use `Fixes #123` if applicable).
-* **Checklist**:
-
- * [ ] My code is clean and follows the style guide
- * [ ] I’ve added or updated tests
- * [ ] I’ve run all tests and they pass
- * [ ] I’ve documented my changes (if necessary)
+- **Title**: Describe what the PR does, clearly and specifically.
+- **Description**: Explain the context. Link related issues (use `Fixes #123` if applicable).
+- **Checklist**:
+ - [ ] My code is clean and follows the style guide
+ - [ ] I’ve added or updated tests
+ - [ ] I’ve run all tests and they pass
+ - [ ] I’ve documented my changes (if necessary)
## Code Review Process
-* PRs are reviewed by maintainers or core contributors.
-* If feedback is given, respond and push updates. Do **not** open new PRs for changes to an existing one.
-* PRs that are incomplete, sloppy, or violate the above will be closed.
+- PRs are reviewed by maintainers or core contributors.
+- If feedback is given, respond and push updates. Do **not** open new PRs for changes to an existing one.
+- PRs that are incomplete, sloppy, or violate the above will be closed.
## Don't Do This
-* Don’t commit directly to `main`
-* Don’t submit multiple unrelated changes in a single PR
-* Don’t ignore CI/test failures
-* Don’t expect hand-holding—read the docs and the source first
+- Don’t commit directly to `main`
+- Don’t submit multiple unrelated changes in a single PR
+- Don’t ignore CI/test failures
+- Don’t expect hand-holding—read the docs and the source first
## Security Issues
@@ -124,4 +126,3 @@ Do **not** open public issues for security vulnerabilities. Email the developmen
## License
By contributing, you agree that your code will be licensed under the same license as the rest of the project.
-
diff --git a/README.md b/README.md
index ec71b5d8..447c4f52 100644
--- a/README.md
+++ b/README.md
@@ -1,4 +1,3 @@
-

[](https://github.com/CompassConnections/Compass/actions/workflows/cd.yml)
[](https://github.com/CompassConnections/Compass/actions/workflows/cd-api.yml)
@@ -34,14 +33,17 @@ No contribution is too small—whether it’s changing a color, resizing a butto
The complete, official list of tasks is available [here on ClickUp](https://sharing.clickup.com/90181043445/l/h/6-901810339879-1/bbfd32f4f4bf64b). If you are working on one task, just assign it to yourself and move its status to "in progress". If there is also a GitHub issue for that task, assign it to yourself as well.
To have edit access to the ClickUp workspace, you need an admin to manually give you permission (one time thing). To do so, use your preferred option:
+
- Ask or DM an admin on [Discord](https://discord.gg/8Vd7jzqjun)
- Email hello@compassmeet.com
- Raise an issue on GitHub
If you want to add tasks without creating an account, you can simply email
+
```
a.t.901810339879.u-276866260.b847aba1-2709-4f17-b4dc-565a6967c234@tasks.clickup.com
```
+
Put the task title in the email subject and the task description in the email content.
Here is a tailored selection of things that would be very useful. If you want to help but don’t know where to start, just ask us on [Discord](https://discord.gg/8Vd7jzqjun).
@@ -105,17 +107,20 @@ Below are the steps to contribute. If you have any trouble or questions, please
### Installation
Fork the [repo](https://github.com/CompassConnections/Compass) on GitHub (button in top right). Then, clone your repo and navigating into it:
+
```bash
git clone https://github.com//Compass.git
cd Compass
```
Install `yarn` (if not already installed):
+
```bash
npm install --global yarn
```
Then, install the dependencies for this project:
+
```bash
yarn install --frozen-lockfile
```
@@ -123,14 +128,17 @@ yarn install --frozen-lockfile
### Tests
Make sure the Jest tests pass:
+
```bash
yarn test
```
+
If they don't and you can't find out why, simply raise an issue! Sometimes it's something on our end that we overlooked.
### Running the Development Server
Start the development server:
+
```bash
yarn dev
```
@@ -227,6 +235,7 @@ looks and feels like the real thing.
Now you can start contributing by making changes and submitting pull requests!
We recommend using a good code editor (VSCode, WebStorm, Cursor, etc.) with Typescript support and a good AI assistant (GitHub Copilot, etc.) to make your life easier. To debug, you can use the browser developer tools (F12), specifically:
+
- Components tab to see the React component tree and props (you need to install the [React Developer Tools](https://react.dev/learn/react-developer-tools) extension)
- Console tab for errors and logs
- Network tab to see the requests and responses
@@ -239,6 +248,7 @@ If you are new to Typescript or the open-source space, you could start with smal
##### Resources
There is a lof of documentation in the [docs](docs) folder and across the repo, namely:
+
- [Next.js.md](docs/Next.js.md) for core fundamentals about our web / page-rendering framework.
- [knowledge.md](docs/knowledge.md) for general information about the project structure.
- [development.md](docs/development.md) for additional instructions, such as adding new profile fields or languages.
@@ -252,22 +262,26 @@ There are a lot of useful scripts you can use in the [scripts](scripts) folder.
### Submission
Add the original repo as upstream for syncing:
+
```bash
git remote add upstream https://github.com/CompassConnections/Compass.git
```
Create a new branch for your changes:
+
```bash
git checkout -b
```
Make changes, then stage and commit:
+
```bash
git add .
git commit -m "Describe your changes"
```
Push branch to your fork:
+
```bash
git push origin
```
@@ -279,6 +293,7 @@ Finally, open a Pull Request on GitHub from your `fork/` → `Compa
Almost all the features will work out of the box, so you can skip this step and come back later if you need to test the following services: email, geolocation.
We can't make the following information public, for security and privacy reasons:
+
- Database, otherwise anyone could access all the user data (including private messages)
- Firebase, otherwise anyone could remove users or modify the media files
- Email, analytics, and location services, otherwise anyone could use the service plans Compass paid for and run up the bill.
@@ -289,4 +304,5 @@ Contributors should use the default keys for local development. Production uses
If you do need one of the few remaining services, you need to set them up and store your own secrets as environment variables. To do so, simply open `.env` and fill in the variables according to the instructions in the file.
## Acknowledgements
+
This project is built on top of [manifold.love](https://github.com/sipec/polylove), an open-source dating platform licensed under the MIT License. We greatly appreciate their work and contributions to open-source, which have significantly aided in the development of some core features such as direct messaging, prompts, and email notifications. We invite the community to explore and contribute to other open-source projects like manifold.love as well, especially if you're interested in functionalities that deviate from Compass' ideals of deep, intentional connections.
diff --git a/SECURITY.md b/SECURITY.md
index ab0c17cd..7c84828c 100644
--- a/SECURITY.md
+++ b/SECURITY.md
@@ -9,4 +9,3 @@
## Reporting a Vulnerability
Contact the development team at hello@compassmeet.com to report a vulnerability. You should receive updates within a week.
-
diff --git a/android/app/capacitor.build.gradle b/android/app/capacitor.build.gradle
index f4fc69cf..f4e3fa4c 100644
--- a/android/app/capacitor.build.gradle
+++ b/android/app/capacitor.build.gradle
@@ -2,8 +2,8 @@
android {
compileOptions {
- sourceCompatibility JavaVersion.VERSION_17
- targetCompatibility JavaVersion.VERSION_17
+ sourceCompatibility JavaVersion.VERSION_21
+ targetCompatibility JavaVersion.VERSION_21
}
}
diff --git a/backend/api/.eslintrc.js b/backend/api/.eslintrc.js
index 8f6f05d2..d1f83573 100644
--- a/backend/api/.eslintrc.js
+++ b/backend/api/.eslintrc.js
@@ -1,7 +1,7 @@
module.exports = {
- plugins: ['lodash', 'unused-imports'],
+ plugins: ['lodash', 'unused-imports', 'simple-import-sort'],
extends: ['eslint:recommended'],
- ignorePatterns: ['dist', 'lib', 'tests', 'coverage'],
+ ignorePatterns: ['dist', 'lib', 'coverage'],
env: {
node: true,
},
@@ -16,9 +16,9 @@ module.exports = {
project: ['./tsconfig.json', './tsconfig.test.json'],
},
rules: {
- "@typescript-eslint/no-empty-object-type": "error", // replaces banning {}
- "@typescript-eslint/no-unsafe-function-type": "error", // replaces banning Function
- "@typescript-eslint/no-wrapper-object-types": "error", // replaces banning String, Number, etc.
+ '@typescript-eslint/no-empty-object-type': 'error', // replaces banning {}
+ '@typescript-eslint/no-unsafe-function-type': 'error', // replaces banning Function
+ '@typescript-eslint/no-wrapper-object-types': 'error', // replaces banning String, Number, etc.
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-extra-semi': 'off',
'@typescript-eslint/no-unused-vars': [
@@ -35,10 +35,9 @@ module.exports = {
},
],
rules: {
- 'linebreak-style': [
- 'error',
- process.platform === 'win32' ? 'windows' : 'unix',
- ],
+ 'linebreak-style': ['error', process.platform === 'win32' ? 'windows' : 'unix'],
'lodash/import-scope': [2, 'member'],
+ 'simple-import-sort/imports': 'error',
+ 'simple-import-sort/exports': 'error',
},
}
diff --git a/backend/api/.prettierignore b/backend/api/.prettierignore
new file mode 100644
index 00000000..d4cf806b
--- /dev/null
+++ b/backend/api/.prettierignore
@@ -0,0 +1,34 @@
+# Dependencies
+node_modules
+.yarn
+
+# Build outputs
+dist
+build
+.next
+out
+lib
+
+# Generated files
+coverage
+*.min.js
+*.min.css
+
+# Database / migrations
+**/*.sql
+
+# Config / lock files
+yarn.lock
+package-lock.json
+pnpm-lock.yaml
+
+# Android / iOS
+android
+ios
+capacitor.config.ts
+
+# Playwright
+tests/reports
+playwright-report
+
+coverage
\ No newline at end of file
diff --git a/backend/api/README.md b/backend/api/README.md
index 4004e91e..c2b91dba 100644
--- a/backend/api/README.md
+++ b/backend/api/README.md
@@ -28,9 +28,11 @@ gcloud config set project YOUR_PROJECT_ID
```
You also need `opentofu` and `docker`. Try running this (from root) on Linux or macOS for a faster install:
+
```bash
./script/setup.sh
```
+
If it doesn't work, you can install them manually (google how to install `opentofu` and `docker` for your OS).
### Setup
@@ -105,8 +107,8 @@ gcloud iam service-accounts keys create keyfile.json --iam-account=ci-deployer@c
##### DNS
-* After deployment, Terraform assigns a static external IP to this resource.
-* You can get it manually:
+- After deployment, Terraform assigns a static external IP to this resource.
+- You can get it manually:
```bash
gcloud compute addresses describe api-lb-ip-2 --global --format="get(address)"
@@ -120,11 +122,11 @@ Since Vercel manages your domain (`compassmeet.com`):
3. Add an **A record** for your API subdomain:
| Type | Name | Value | TTL |
-|------|------|--------------|-------|
+| ---- | ---- | ------------ | ----- |
| A | api | 34.123.45.67 | 600 s |
-* `Name` is just the subdomain: `api` → `api.compassmeet.com`.
-* `Value` is the **external IP of the LB** from step 1.
+- `Name` is just the subdomain: `api` → `api.compassmeet.com`.
+- `Value` is the **external IP of the LB** from step 1.
Verify connectivity
From your local machine:
@@ -135,8 +137,8 @@ ping -c 3 api.compassmeet.com
curl -I https://api.compassmeet.com
```
-* `nslookup` should return the LB IP (`34.123.45.67`).
-* `curl -I` should return `200 OK` from your service.
+- `nslookup` should return the LB IP (`34.123.45.67`).
+- `curl -I` should return `200 OK` from your service.
If SSL isn’t ready (may take 15 mins), check LB logs:
@@ -167,6 +169,7 @@ In root directory, run the local api with hot reload, along with all the other b
To deploy the backend code, simply increment the version number in [package.json](package.json) and push to the `main` branch.
Or if you have access to the project on google cloud, run in this directory:
+
```bash
./deploy-api.sh prod
```
@@ -195,4 +198,5 @@ docker rmi -f $(docker images -aq)
The API doc is available at https://api.compassmeet.com. It's dynamically prepared in [app.ts](src/app.ts).
### Todo (Tests)
-- [ ] Finish get-supabase-token unit test when endpoint is implemented
\ No newline at end of file
+
+- [ ] Finish get-supabase-token unit test when endpoint is implemented
diff --git a/backend/api/ecosystem.config.js b/backend/api/ecosystem.config.js
index 9094d11b..de4336de 100644
--- a/backend/api/ecosystem.config.js
+++ b/backend/api/ecosystem.config.js
@@ -1,21 +1,21 @@
module.exports = {
- apps: [
- {
- name: "api",
- script: "node",
- args: "--dns-result-order=ipv4first backend/api/lib/serve.js",
- env: {
- NODE_ENV: "production",
- NODE_PATH: "/usr/src/app/node_modules", // <- ensures Node finds tsconfig-paths
- PORT: 80,
- },
- instances: 1,
- exec_mode: "fork",
- autorestart: true,
- watch: false,
- // 4 GB on the box, give 3 GB to the JS heap
- node_args: "--max-old-space-size=3072",
- max_memory_restart: "3500M"
- }
- ]
-};
+ apps: [
+ {
+ name: 'api',
+ script: 'node',
+ args: '--dns-result-order=ipv4first backend/api/lib/serve.js',
+ env: {
+ NODE_ENV: 'production',
+ NODE_PATH: '/usr/src/app/node_modules', // <- ensures Node finds tsconfig-paths
+ PORT: 80,
+ },
+ instances: 1,
+ exec_mode: 'fork',
+ autorestart: true,
+ watch: false,
+ // 4 GB on the box, give 3 GB to the JS heap
+ node_args: '--max-old-space-size=3072',
+ max_memory_restart: '3500M',
+ },
+ ],
+}
diff --git a/backend/api/jest.config.js b/backend/api/jest.config.js
index b443f00d..361aa9ba 100644
--- a/backend/api/jest.config.js
+++ b/backend/api/jest.config.js
@@ -1,31 +1,28 @@
module.exports = {
- preset: 'ts-jest',
- testEnvironment: 'node',
+ preset: 'ts-jest',
+ testEnvironment: 'node',
- rootDir: '.',
- testMatch: [
- "/tests/**/*.test.ts",
- "/tests/**/*.spec.ts"
+ rootDir: '.',
+ testMatch: ['/tests/**/*.test.ts', '/tests/**/*.spec.ts'],
+
+ moduleNameMapper: {
+ '^api/(.*)$': '/src/$1',
+ '^shared/(.*)$': '/../shared/src/$1',
+ '^common/(.*)$': '/../../common/src/$1',
+ '^email/(.*)$': '/../email/emails/$1',
+ },
+
+ moduleFileExtensions: ['tsx', 'ts', 'js', 'json'],
+ clearMocks: true,
+
+ transform: {
+ '^.+\\.tsx?$': [
+ 'ts-jest',
+ {
+ tsconfig: '/tsconfig.test.json',
+ },
],
+ },
- moduleNameMapper: {
- "^api/(.*)$": "/src/$1",
- "^shared/(.*)$": "/../shared/src/$1",
- "^common/(.*)$": "/../../common/src/$1",
- "^email/(.*)$": "/../email/emails/$1"
- },
-
- moduleFileExtensions: ["tsx","ts", "js", "json"],
- clearMocks: true,
-
- globals: {
- 'ts-jest': {
- tsconfig: "/tsconfig.test.json"
- }
- },
-
- collectCoverageFrom: [
- "src/**/*.{ts,tsx}",
- "!src/**/*.d.ts"
- ],
-};
+ collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'],
+}
diff --git a/backend/api/package.json b/backend/api/package.json
index c090dbbf..b7249d78 100644
--- a/backend/api/package.json
+++ b/backend/api/package.json
@@ -5,22 +5,21 @@
"private": true,
"scripts": {
"watch:serve": "tsx watch src/serve.ts",
- "watch:compile": "npx concurrently \"tsc -b --watch --preserveWatchOutput\" \"(cd ../../common && tsc-alias --watch)\" \"(cd ../shared && tsc-alias --watch)\" \"(cd ../email && tsc-alias --watch)\" \"tsc-alias --watch\"",
+ "watch:compile": "npx concurrently \"(cd ../../common && tsc --watch)\" \"(cd ../shared && tsc --watch)\" \"(cd ../email && tsc --watch)\" \"tsc --watch --preserveWatchOutput\" \"tsc-alias --watch\" \"(cd ../../common && tsc-alias --watch)\" \"(cd ../shared && tsc-alias --watch)\" \"(cd ../email && tsc-alias --watch)\"",
"dev": "yarn watch:serve",
"prod": "npx concurrently -n COMPILE,SERVER -c cyan,green \"yarn watch:compile\" \"yarn watch:serve\"",
"build": "yarn compile && yarn dist:clean && yarn dist:copy",
"build:fast": "yarn compile && yarn dist:copy",
"clean": "rm -rf lib && (cd ../../common && rm -rf lib) && (cd ../shared && rm -rf lib) && (cd ../email && rm -rf lib)",
- "compile": "tsc -b && tsc-alias && (cd ../../common && tsc-alias) && (cd ../shared && tsc-alias) && (cd ../email && tsc-alias) && cp -r src/public/ lib/",
+ "compile": "(cd ../../common && tsc) && (cd ../shared && tsc) && (cd ../email && tsc) && tsc && tsc-alias && (cd ../../common && tsc-alias) && (cd ../shared && tsc-alias) && (cd ../email && tsc-alias) && cp -r src/public/ lib/",
"debug": "nodemon -r tsconfig-paths/register --watch src -e ts --watch ../../common/src --watch ../shared/src --exec \"yarn build && node --inspect-brk src/serve.ts\"",
"dist": "yarn dist:clean && yarn dist:copy",
"dist:clean": "rm -rf dist && mkdir -p dist/common/lib dist/backend/shared/lib dist/backend/api/lib dist/backend/email/lib",
"dist:copy": "rsync -a --delete ../../common/lib/ dist/common/lib && rsync -a --delete ../shared/lib/ dist/backend/shared/lib && rsync -a --delete ../email/lib/ dist/backend/email/lib && rsync -a --delete ./lib/* dist/backend/api/lib && cp ../../yarn.lock dist && cp package.json dist && cp package.json dist/backend/api && cp metadata.json dist && cp metadata.json dist/backend/api",
"watch": "tsc -w",
- "verify": "yarn --cwd=../.. verify",
"lint": "npx eslint . --max-warnings 0",
"lint-fix": "npx eslint . --fix",
- "verify:dir": "npx eslint . --max-warnings 0",
+ "typecheck": "npx tsc --noEmit",
"regen-types": "cd ../supabase && make ENV=prod regen-types",
"regen-types-dev": "cd ../supabase && make ENV=dev regen-types-dev",
"test": "jest --config jest.config.js",
diff --git a/backend/api/src/app.ts b/backend/api/src/app.ts
index c78872f6..d78b066d 100644
--- a/backend/api/src/app.ts
+++ b/backend/api/src/app.ts
@@ -1,91 +1,93 @@
-import {API, type APIPath} from 'common/api/schema'
-import {APIError, pathWithPrefix} from 'common/api/utils'
-import cors from 'cors'
-import * as crypto from 'crypto'
-import express, {type ErrorRequestHandler, type RequestHandler} from 'express'
+import path from 'node:path'
import {hrtime} from 'node:process'
-import {withMonitoringContext} from 'shared/monitoring/context'
-import {log} from 'shared/monitoring/log'
-import {metrics} from 'shared/monitoring/metrics'
-import {banUser} from './ban-user'
-import {blockUser, unblockUser} from './block-user'
-import {getCompatibleProfilesHandler} from './compatible-profiles'
-import {createComment} from './create-comment'
-import {createCompatibilityQuestion} from './create-compatibility-question'
-import {setCompatibilityAnswer} from './set-compatibility-answer'
-import {deleteCompatibilityAnswer} from './delete-compatibility-answer'
-import {createProfile} from './create-profile'
-import {createUser} from './create-user'
-import {getCompatibilityQuestions} from './get-compatibililty-questions'
-import {getLikesAndShips} from './get-likes-and-ships'
-import {getProfileAnswers} from './get-profile-answers'
-import {getProfiles} from './get-profiles'
-import {getSupabaseToken} from './get-supabase-token'
-import {getMe} from './get-me'
-import {hasFreeLike} from './has-free-like'
-import {health} from './health'
-import {type APIHandler, typedEndpoint} from './helpers/endpoint'
-import {hideComment} from './hide-comment'
-import {likeProfile} from './like-profile'
-import {markAllNotifsRead} from './mark-all-notifications-read'
-import {removePinnedPhoto} from './remove-pinned-photo'
-import {report} from './report'
-import {searchLocation} from './search-location'
-import {searchNearCity} from './search-near-city'
-import {shipProfiles} from './ship-profiles'
-import {starProfile} from './star-profile'
-import {updateProfile} from './update-profile'
-import {updateMe} from './update-me'
-import {deleteMe} from './delete-me'
-import {getCurrentPrivateUser} from './get-current-private-user'
-import {createPrivateUserMessage} from './create-private-user-message'
+
+import {contact} from 'api/contact'
+import {createVote} from 'api/create-vote'
+import {deleteMessage} from 'api/delete-message'
+import {editMessage} from 'api/edit-message'
+import {getHiddenProfiles} from 'api/get-hidden-profiles'
+import {getMessagesCount} from 'api/get-messages-count'
+import {getOptions} from 'api/get-options'
import {
getChannelMemberships,
getChannelMessagesEndpoint,
getLastSeenChannelTime,
setChannelLastSeenTime,
} from 'api/get-private-messages'
-import {searchUsers} from './search-users'
-import {createPrivateUserMessageChannel} from './create-private-user-message-channel'
-import {leavePrivateUserMessageChannel} from './leave-private-user-message-channel'
-import {updatePrivateUserMessageChannel} from './update-private-user-message-channel'
-import {getNotifications} from './get-notifications'
-import {updateNotifSettings} from './update-notif-setting'
-import {setLastOnlineTime} from './set-last-online-time'
-import swaggerUi from "swagger-ui-express"
-import {sendSearchNotifications} from "api/send-search-notifications";
-import {sendDiscordMessage} from "common/discord/core";
-import {getMessagesCount} from "api/get-messages-count";
-import {createVote} from "api/create-vote";
-import {vote} from "api/vote";
-import {contact} from "api/contact";
-import {saveSubscription} from "api/save-subscription";
-import {createBookmarkedSearch} from './create-bookmarked-search'
-import {deleteBookmarkedSearch} from './delete-bookmarked-search'
-import {OpenAPIV3} from 'openapi-types';
-import {version as pkgVersion} from './../package.json'
+import {getUser} from 'api/get-user'
+import {hideProfile} from 'api/hide-profile'
+import {reactToMessage} from 'api/react-to-message'
+import {saveSubscription} from 'api/save-subscription'
+import {saveSubscriptionMobile} from 'api/save-subscription-mobile'
+import {sendSearchNotifications} from 'api/send-search-notifications'
+import {localSendTestEmail} from 'api/test'
+import {unhideProfile} from 'api/unhide-profile'
+import {updateOptions} from 'api/update-options'
+import {vote} from 'api/vote'
+import {API, type APIPath} from 'common/api/schema'
+import {APIError, pathWithPrefix} from 'common/api/utils'
+import {sendDiscordMessage} from 'common/discord/core'
+import {IS_LOCAL} from 'common/hosting/constants'
+import cors from 'cors'
+import * as crypto from 'crypto'
+import express, {type ErrorRequestHandler, type RequestHandler} from 'express'
+import {OpenAPIV3} from 'openapi-types'
+import {withMonitoringContext} from 'shared/monitoring/context'
+import {log} from 'shared/monitoring/log'
+import {metrics} from 'shared/monitoring/metrics'
+import swaggerUi from 'swagger-ui-express'
+import {z, ZodFirstPartyTypeKind, ZodTypeAny} from 'zod'
+
import {git} from './../metadata.json'
-import {z, ZodFirstPartyTypeKind, ZodTypeAny} from "zod";
-import {getUser} from "api/get-user";
-import {localSendTestEmail} from "api/test";
-import path from "node:path";
-import {saveSubscriptionMobile} from "api/save-subscription-mobile";
-import {IS_LOCAL} from "common/hosting/constants";
-import {editMessage} from "api/edit-message";
-import {reactToMessage} from "api/react-to-message";
-import {deleteMessage} from "api/delete-message";
-import {updateOptions} from "api/update-options";
-import {getOptions} from "api/get-options";
-import {hideProfile} from "api/hide-profile";
-import {unhideProfile} from "api/unhide-profile";
-import {getHiddenProfiles} from "api/get-hidden-profiles";
-import {getUserDataExport} from "./get-user-data-export";
-import {getEvents} from "./get-events";
-import {createEvent} from "./create-event";
-import {rsvpEvent} from "./rsvp-event";
-import {cancelRsvp} from "./cancel-rsvp";
-import {cancelEvent} from "./cancel-event";
-import {updateEvent} from "./update-event";
+import {version as pkgVersion} from './../package.json'
+import {banUser} from './ban-user'
+import {blockUser, unblockUser} from './block-user'
+import {cancelEvent} from './cancel-event'
+import {cancelRsvp} from './cancel-rsvp'
+import {getCompatibleProfilesHandler} from './compatible-profiles'
+import {createBookmarkedSearch} from './create-bookmarked-search'
+import {createComment} from './create-comment'
+import {createCompatibilityQuestion} from './create-compatibility-question'
+import {createEvent} from './create-event'
+import {createPrivateUserMessage} from './create-private-user-message'
+import {createPrivateUserMessageChannel} from './create-private-user-message-channel'
+import {createProfile} from './create-profile'
+import {createUser} from './create-user'
+import {deleteBookmarkedSearch} from './delete-bookmarked-search'
+import {deleteCompatibilityAnswer} from './delete-compatibility-answer'
+import {deleteMe} from './delete-me'
+import {getCompatibilityQuestions} from './get-compatibililty-questions'
+import {getCurrentPrivateUser} from './get-current-private-user'
+import {getEvents} from './get-events'
+import {getLikesAndShips} from './get-likes-and-ships'
+import {getMe} from './get-me'
+import {getNotifications} from './get-notifications'
+import {getProfileAnswers} from './get-profile-answers'
+import {getProfiles} from './get-profiles'
+import {getSupabaseToken} from './get-supabase-token'
+import {getUserDataExport} from './get-user-data-export'
+import {hasFreeLike} from './has-free-like'
+import {health} from './health'
+import {type APIHandler, typedEndpoint} from './helpers/endpoint'
+import {hideComment} from './hide-comment'
+import {leavePrivateUserMessageChannel} from './leave-private-user-message-channel'
+import {likeProfile} from './like-profile'
+import {markAllNotifsRead} from './mark-all-notifications-read'
+import {removePinnedPhoto} from './remove-pinned-photo'
+import {report} from './report'
+import {rsvpEvent} from './rsvp-event'
+import {searchLocation} from './search-location'
+import {searchNearCity} from './search-near-city'
+import {searchUsers} from './search-users'
+import {setCompatibilityAnswer} from './set-compatibility-answer'
+import {setLastOnlineTime} from './set-last-online-time'
+import {shipProfiles} from './ship-profiles'
+import {starProfile} from './star-profile'
+import {updateEvent} from './update-event'
+import {updateMe} from './update-me'
+import {updateNotifSettings} from './update-notif-setting'
+import {updatePrivateUserMessageChannel} from './update-private-user-message-channel'
+import {updateProfile} from './update-profile'
// const corsOptions: CorsOptions = {
// origin: ['*'], // Only allow requests from this domain
@@ -104,9 +106,7 @@ function cacheController(policy?: string): RequestHandler {
const requestMonitoring: RequestHandler = (req, _res, next) => {
const traceContext = req.get('X-Cloud-Trace-Context')
- const traceId = traceContext
- ? traceContext.split('/')[0]
- : crypto.randomUUID()
+ const traceId = traceContext ? traceContext.split('/')[0] : crypto.randomUUID()
const context = {endpoint: req.path, traceId}
withMonitoringContext(context, () => {
const startTs = hrtime.bigint()
@@ -123,7 +123,7 @@ const apiErrorHandler: ErrorRequestHandler = (error, _req, res, _next) => {
if (error instanceof APIError) {
log.info(error)
if (!res.headersSent) {
- const output: { [k: string]: unknown } = {message: error.message}
+ const output: {[k: string]: unknown} = {message: error.message}
if (error.details != null) {
output.details = error.details
}
@@ -140,92 +140,91 @@ const apiErrorHandler: ErrorRequestHandler = (error, _req, res, _next) => {
export const app = express()
app.use(requestMonitoring)
+const schemaCache = new WeakMap()
-const schemaCache = new WeakMap();
-
-export function zodToOpenApiSchema(zodObj: ZodTypeAny,): any {
+export function zodToOpenApiSchema(zodObj: ZodTypeAny): any {
if (schemaCache.has(zodObj)) {
- return schemaCache.get(zodObj);
+ return schemaCache.get(zodObj)
}
- const def: any = (zodObj as any)._def;
- const typeName = def.typeName as ZodFirstPartyTypeKind;
+ const def: any = (zodObj as any)._def
+ const typeName = def.typeName as ZodFirstPartyTypeKind
// Placeholder so recursive references can point here
- const placeholder: any = {};
- schemaCache.set(zodObj, placeholder);
+ const placeholder: any = {}
+ schemaCache.set(zodObj, placeholder)
- let schema: any;
+ let schema: any
switch (typeName) {
case 'ZodString':
- schema = {type: 'string'};
- break;
+ schema = {type: 'string'}
+ break
case 'ZodNumber':
- schema = {type: 'number'};
- break;
+ schema = {type: 'number'}
+ break
case 'ZodBoolean':
- schema = {type: 'boolean'};
- break;
+ schema = {type: 'boolean'}
+ break
case 'ZodEnum':
- schema = {type: 'string', enum: def.values};
- break;
+ schema = {type: 'string', enum: def.values}
+ break
case 'ZodArray':
- schema = {type: 'array', items: zodToOpenApiSchema(def.type)};
- break;
+ schema = {type: 'array', items: zodToOpenApiSchema(def.type)}
+ break
case 'ZodObject': {
- const shape = def.shape();
- const properties: Record = {};
- const required: string[] = [];
+ const shape = def.shape()
+ const properties: Record = {}
+ const required: string[] = []
for (const key in shape) {
- const child = shape[key];
- properties[key] = zodToOpenApiSchema(child);
- if (!child.isOptional()) required.push(key);
+ const child = shape[key]
+ properties[key] = zodToOpenApiSchema(child)
+ if (!child.isOptional()) required.push(key)
}
schema = {
type: 'object',
properties,
...(required.length ? {required} : {}),
- };
- break;
+ }
+ break
}
case 'ZodRecord':
schema = {
type: 'object',
additionalProperties: zodToOpenApiSchema(def.valueType),
- };
- break;
+ }
+ break
case 'ZodIntersection': {
- const left = zodToOpenApiSchema(def.left);
- const right = zodToOpenApiSchema(def.right);
- schema = {allOf: [left, right]};
- break;
+ const left = zodToOpenApiSchema(def.left)
+ const right = zodToOpenApiSchema(def.right)
+ schema = {allOf: [left, right]}
+ break
}
case 'ZodLazy':
- schema = {type: 'object', description: 'Lazy schema - details omitted'};
- break;
+ schema = {type: 'object', description: 'Lazy schema - details omitted'}
+ break
case 'ZodUnion':
schema = {
oneOf: def.options.map((opt: ZodTypeAny) => zodToOpenApiSchema(opt)),
- };
- break;
+ }
+ break
default:
- schema = {type: 'string'}; // fallback for unhandled
+ schema = {type: 'string'} // fallback for unhandled
}
- Object.assign(placeholder, schema);
- return schema;
+ Object.assign(placeholder, schema)
+ return schema
}
function generateSwaggerPaths(api: typeof API) {
- const paths: Record = {};
+ const paths: Record = {}
for (const [route, config] of Object.entries(api)) {
- const pathKey = '/' + route.replace(/_/g, '-'); // optional: convert underscores to dashes
- const method = config.method.toLowerCase();
- const summary = (config as any).summary ?? route;
+ const pathKey = '/' + route.replace(/_/g, '-') // optional: convert underscores to dashes
+ const method = config.method.toLowerCase()
+ const summary = (config as any).summary ?? route
// Include props in request body for POST/PUT
const operation: any = {
@@ -241,7 +240,7 @@ function generateSwaggerPaths(api: typeof API) {
},
},
},
- };
+ }
// Include props in request body for POST/PUT
if (config.props && ['post', 'put', 'patch'].includes(method)) {
@@ -252,26 +251,26 @@ function generateSwaggerPaths(api: typeof API) {
schema: zodToOpenApiSchema(config.props),
},
},
- };
+ }
}
// Include props as query parameters for GET/DELETE
if (config.props && ['get', 'delete'].includes(method)) {
- const shape = (config.props as z.ZodObject)._def.shape();
+ const shape = (config.props as z.ZodObject)._def.shape()
operation.parameters = Object.entries(shape).map(([key, zodType]) => {
const typeMap: Record = {
ZodString: 'string',
ZodNumber: 'number',
ZodBoolean: 'boolean',
- };
- const t = zodType as z.ZodTypeAny; // assert type to ZodTypeAny
+ }
+ const t = zodType as z.ZodTypeAny // assert type to ZodTypeAny
return {
name: key,
in: 'query',
required: !(t.isOptional ?? false),
schema: {type: typeMap[t._def.typeName] ?? 'string'},
- };
- });
+ }
+ })
}
paths[pathKey] = {
@@ -279,25 +278,24 @@ function generateSwaggerPaths(api: typeof API) {
}
if (config.authed) {
- operation.security = [{BearerAuth: []}];
+ operation.security = [{BearerAuth: []}]
}
}
- return paths;
+ return paths
}
-
const swaggerDocument: OpenAPIV3.Document = {
- openapi: "3.0.0",
+ openapi: '3.0.0',
info: {
- title: "Compass API",
+ title: 'Compass API',
description: `Compass is a free, open-source platform to help people form deep, meaningful, and lasting connections — whether platonic, romantic, or collaborative. It’s made possible by contributions from the community, including code, ideas, feedback, and donations. Unlike typical apps, Compass prioritizes values, interests, and personality over swipes and ads, giving you full control over who you discover and how you connect.\n Git: ${git.commitDate} (${git.revision}).`,
version: pkgVersion,
contact: {
- name: "Compass",
- email: "hello@compassmeet.com",
- url: "https://compassmeet.com"
- }
+ name: 'Compass',
+ email: 'hello@compassmeet.com',
+ url: 'https://compassmeet.com',
+ },
},
paths: generateSwaggerPaths(API),
components: {
@@ -313,17 +311,17 @@ const swaggerDocument: OpenAPIV3.Document = {
name: 'x-api-key',
},
},
- }
-} as OpenAPIV3.Document;
+ },
+} as OpenAPIV3.Document
// Triggers Missing parameter name at index 3: *; visit https://git.new/pathToRegexpError for info
// May not be necessary
// app.options('*', allowCorsUnrestricted)
-const handlers: { [k in APIPath]: APIHandler } = {
+const handlers: {[k in APIPath]: APIHandler} = {
'ban-user': banUser,
'compatible-profiles': getCompatibleProfilesHandler,
- 'contact': contact,
+ contact: contact,
'create-bookmarked-search': createBookmarkedSearch,
'create-comment': createComment,
'create-compatibility-question': createCompatibilityQuestion,
@@ -378,7 +376,7 @@ const handlers: { [k in APIPath]: APIHandler } = {
'user/by-id/:id': getUser,
'user/by-id/:id/block': blockUser,
'user/by-id/:id/unblock': unblockUser,
- 'vote': vote,
+ vote: vote,
// 'user/:username': getUser,
// 'user/:username/lite': getDisplayUser,
// 'user/by-id/:id/lite': getDisplayUser,
@@ -396,7 +394,7 @@ const handlers: { [k in APIPath]: APIHandler } = {
Object.entries(handlers).forEach(([path, handler]) => {
const api = API[path as APIPath]
const cache = cacheController((api as any).cache)
- const url = pathWithPrefix('/' + path as APIPath)
+ const url = pathWithPrefix(('/' + path) as APIPath)
const apiRoute = [
url,
@@ -419,75 +417,73 @@ Object.entries(handlers).forEach(([path, handler]) => {
})
// Internal Endpoints
-app.post(pathWithPrefix("/internal/send-search-notifications"),
- async (req, res) => {
- const apiKey = req.header("x-api-key");
- if (apiKey !== process.env.COMPASS_API_KEY) {
- return res.status(401).json({error: "Unauthorized"});
- }
-
- try {
- const result = await sendSearchNotifications()
- return res.status(200).json(result)
- } catch (err) {
- console.error("Failed to send notifications:", err);
- await sendDiscordMessage(
- "Failed to send [daily notifications](https://console.cloud.google.com/cloudscheduler?project=compass-130ba) for bookmarked searches...",
- "health"
- )
- return res.status(500).json({error: "Internal server error"});
- }
+app.post(pathWithPrefix('/internal/send-search-notifications'), async (req, res) => {
+ const apiKey = req.header('x-api-key')
+ if (apiKey !== process.env.COMPASS_API_KEY) {
+ return res.status(401).json({error: 'Unauthorized'})
}
-);
+
+ try {
+ const result = await sendSearchNotifications()
+ return res.status(200).json(result)
+ } catch (err) {
+ console.error('Failed to send notifications:', err)
+ await sendDiscordMessage(
+ 'Failed to send [daily notifications](https://console.cloud.google.com/cloudscheduler?project=compass-130ba) for bookmarked searches...',
+ 'health',
+ )
+ return res.status(500).json({error: 'Internal server error'})
+ }
+})
const responses = {
200: {
- description: "Request successful",
+ description: 'Request successful',
content: {
- "application/json": {
+ 'application/json': {
schema: {
- type: "object",
+ type: 'object',
properties: {
- status: {type: "string", example: "success"}
+ status: {type: 'string', example: 'success'},
},
},
},
},
},
401: {
- description: "Unauthorized (e.g., invalid or missing API key)",
+ description: 'Unauthorized (e.g., invalid or missing API key)',
content: {
- "application/json": {
+ 'application/json': {
schema: {
- type: "object",
+ type: 'object',
properties: {
- error: {type: "string", example: "Unauthorized"},
+ error: {type: 'string', example: 'Unauthorized'},
},
},
},
},
},
500: {
- description: "Internal server error during request processing",
+ description: 'Internal server error during request processing',
content: {
- "application/json": {
+ 'application/json': {
schema: {
- type: "object",
+ type: 'object',
properties: {
- error: {type: "string", example: "Internal server error"},
+ error: {type: 'string', example: 'Internal server error'},
},
},
},
},
},
-};
+}
-swaggerDocument.paths["/internal/send-search-notifications"] = {
+swaggerDocument.paths['/internal/send-search-notifications'] = {
post: {
- summary: "Trigger daily search notifications",
+ summary: 'Trigger daily search notifications',
description:
- "Internal endpoint used by Compass schedulers to send daily notifications for bookmarked searches. Requires a valid `x-api-key` header.",
- tags: ["Internal"],
+ 'Internal endpoint used by Compass schedulers to send daily notifications for bookmarked searches. Requires a valid `x-api-key` header.',
+ tags: ['Internal'],
security: [
{
ApiKeyAuth: [],
@@ -500,28 +496,25 @@ swaggerDocument.paths["/internal/send-search-notifications"] = {
},
} as any
-
// Local Endpoints
if (IS_LOCAL) {
- app.post(pathWithPrefix("/local/send-test-email"),
- async (req, res) => {
- if (!IS_LOCAL) {
- return res.status(401).json({error: "Unauthorized"});
- }
-
- try {
- const result = await localSendTestEmail()
- return res.status(200).json(result)
- } catch (err) {
- return res.status(500).json({error: err});
- }
+ app.post(pathWithPrefix('/local/send-test-email'), async (req, res) => {
+ if (!IS_LOCAL) {
+ return res.status(401).json({error: 'Unauthorized'})
}
- );
- swaggerDocument.paths["/local/send-test-email"] = {
+
+ try {
+ const result = await localSendTestEmail()
+ return res.status(200).json(result)
+ } catch (err) {
+ return res.status(500).json({error: err})
+ }
+ })
+ swaggerDocument.paths['/local/send-test-email'] = {
post: {
- summary: "Send a test email",
- description: "Local endpoint to send a test email.",
- tags: ["Local"],
+ summary: 'Send a test email',
+ description: 'Local endpoint to send a test email.',
+ tags: ['Local'],
requestBody: {
required: false,
},
@@ -530,8 +523,7 @@ if (IS_LOCAL) {
} as any
}
-
-const rootPath = pathWithPrefix("/")
+const rootPath = pathWithPrefix('/')
app.get(
rootPath,
swaggerUi.setup(swaggerDocument, {
@@ -547,7 +539,7 @@ app.get(
)
app.use(rootPath, swaggerUi.serve)
-app.use(express.static(path.join(__dirname, 'public')));
+app.use(express.static(path.join(__dirname, 'public')))
app.use(allowCorsUnrestricted, (req, res) => {
if (req.method === 'OPTIONS') {
diff --git a/backend/api/src/ban-user.ts b/backend/api/src/ban-user.ts
index dd55933e..423aff94 100644
--- a/backend/api/src/ban-user.ts
+++ b/backend/api/src/ban-user.ts
@@ -1,13 +1,13 @@
-import { APIError, APIHandler } from 'api/helpers/endpoint'
-import { trackPublicEvent } from 'shared/analytics'
-import { throwErrorIfNotMod } from 'shared/helpers/auth'
-import { isAdminId } from 'common/envs/constants'
-import { log } from 'shared/utils'
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { updateUser } from 'shared/supabase/users'
+import {APIError, APIHandler} from 'api/helpers/endpoint'
+import {isAdminId} from 'common/envs/constants'
+import {trackPublicEvent} from 'shared/analytics'
+import {throwErrorIfNotMod} from 'shared/helpers/auth'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {updateUser} from 'shared/supabase/users'
+import {log} from 'shared/utils'
export const banUser: APIHandler<'ban-user'> = async (body, auth) => {
- const { userId, unban } = body
+ const {userId, unban} = body
const db = createSupabaseDirectClient()
await throwErrorIfNotMod(auth.uid)
if (isAdminId(userId)) throw new APIError(403, 'Cannot ban admin')
diff --git a/backend/api/src/block-user.ts b/backend/api/src/block-user.ts
index 4d776566..fc661bb2 100644
--- a/backend/api/src/block-user.ts
+++ b/backend/api/src/block-user.ts
@@ -1,12 +1,10 @@
-import { APIError, APIHandler } from './helpers/endpoint'
-import { FieldVal } from 'shared/supabase/utils'
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { updatePrivateUser } from 'shared/supabase/users'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {updatePrivateUser} from 'shared/supabase/users'
+import {FieldVal} from 'shared/supabase/utils'
-export const blockUser: APIHandler<'user/by-id/:id/block'> = async (
- { id },
- auth
-) => {
+import {APIError, APIHandler} from './helpers/endpoint'
+
+export const blockUser: APIHandler<'user/by-id/:id/block'> = async ({id}, auth) => {
if (auth.uid === id) throw new APIError(400, 'You cannot block yourself')
const pg = createSupabaseDirectClient()
@@ -20,10 +18,7 @@ export const blockUser: APIHandler<'user/by-id/:id/block'> = async (
})
}
-export const unblockUser: APIHandler<'user/by-id/:id/unblock'> = async (
- { id },
- auth
-) => {
+export const unblockUser: APIHandler<'user/by-id/:id/unblock'> = async ({id}, auth) => {
const pg = createSupabaseDirectClient()
await pg.tx(async (tx) => {
await updatePrivateUser(tx, auth.uid, {
diff --git a/backend/api/src/cancel-event.ts b/backend/api/src/cancel-event.ts
index 8bae9d2d..e9268bd9 100644
--- a/backend/api/src/cancel-event.ts
+++ b/backend/api/src/cancel-event.ts
@@ -1,7 +1,7 @@
import {APIError, APIHandler} from 'api/helpers/endpoint'
+import {tryCatch} from 'common/util/try-catch'
import {createSupabaseDirectClient} from 'shared/supabase/init'
import {update} from 'shared/supabase/utils'
-import {tryCatch} from 'common/util/try-catch'
export const cancelEvent: APIHandler<'cancel-event'> = async (body, auth) => {
const pg = createSupabaseDirectClient()
@@ -15,7 +15,7 @@ export const cancelEvent: APIHandler<'cancel-event'> = async (body, auth) => {
`SELECT id, creator_id, status
FROM events
WHERE id = $1`,
- [body.eventId]
+ [body.eventId],
)
if (!event) {
@@ -35,7 +35,7 @@ export const cancelEvent: APIHandler<'cancel-event'> = async (body, auth) => {
update(pg, 'events', 'id', {
status: 'cancelled',
id: body.eventId,
- })
+ }),
)
if (error) {
diff --git a/backend/api/src/cancel-rsvp.ts b/backend/api/src/cancel-rsvp.ts
index 74b7b07b..dd822d4e 100644
--- a/backend/api/src/cancel-rsvp.ts
+++ b/backend/api/src/cancel-rsvp.ts
@@ -1,6 +1,6 @@
import {APIError, APIHandler} from 'api/helpers/endpoint'
-import {createSupabaseDirectClient} from 'shared/supabase/init'
import {tryCatch} from 'common/util/try-catch'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
export const cancelRsvp: APIHandler<'cancel-rsvp'> = async (body, auth) => {
const pg = createSupabaseDirectClient()
@@ -13,7 +13,7 @@ export const cancelRsvp: APIHandler<'cancel-rsvp'> = async (body, auth) => {
FROM events_participants
WHERE event_id = $1
AND user_id = $2`,
- [body.eventId, auth.uid]
+ [body.eventId, auth.uid],
)
if (!rsvp) {
@@ -26,8 +26,8 @@ export const cancelRsvp: APIHandler<'cancel-rsvp'> = async (body, auth) => {
`DELETE
FROM events_participants
WHERE id = $1`,
- [rsvp.id]
- )
+ [rsvp.id],
+ ),
)
if (error) {
diff --git a/backend/api/src/compatible-profiles.ts b/backend/api/src/compatible-profiles.ts
index 8e31929b..fef356d4 100644
--- a/backend/api/src/compatible-profiles.ts
+++ b/backend/api/src/compatible-profiles.ts
@@ -1,13 +1,11 @@
import {type APIHandler} from 'api/helpers/endpoint'
-import {createSupabaseDirectClient} from "shared/supabase/init";
+import {createSupabaseDirectClient} from 'shared/supabase/init'
export const getCompatibleProfilesHandler: APIHandler<'compatible-profiles'> = async (props) => {
return getCompatibleProfiles(props.userId)
}
-export const getCompatibleProfiles = async (
- userId: string,
-) => {
+export const getCompatibleProfiles = async (userId: string) => {
const pg = createSupabaseDirectClient()
const scores = await pg.map(
`select *
@@ -15,7 +13,7 @@ export const getCompatibleProfiles = async (
where score is not null
and (user_id_1 = $1 or user_id_2 = $1)`,
[userId],
- (r) => [r.user_id_1 == userId ? r.user_id_2 : r.user_id_1, {score: r.score}] as const
+ (r) => [r.user_id_1 == userId ? r.user_id_2 : r.user_id_1, {score: r.score}] as const,
)
const profileCompatibilityScores = Object.fromEntries(scores)
diff --git a/backend/api/src/contact.ts b/backend/api/src/contact.ts
index af4a289c..7a92665f 100644
--- a/backend/api/src/contact.ts
+++ b/backend/api/src/contact.ts
@@ -1,24 +1,22 @@
-import {APIError, APIHandler} from './helpers/endpoint'
+import {sendDiscordMessage} from 'common/discord/core'
+import {jsonToMarkdown} from 'common/md'
+import {tryCatch} from 'common/util/try-catch'
import {createSupabaseDirectClient} from 'shared/supabase/init'
import {insert} from 'shared/supabase/utils'
-import {tryCatch} from 'common/util/try-catch'
-import {sendDiscordMessage} from "common/discord/core";
-import {jsonToMarkdown} from "common/md";
+
+import {APIError, APIHandler} from './helpers/endpoint'
// Stores a contact message into the `contact` table
// Web sends TipTap JSON in `content`; we store it as string in `description`.
// If optional content metadata is provided, we include it; otherwise we fall back to user-centric defaults.
-export const contact: APIHandler<'contact'> = async (
- {content, userId},
- _auth
-) => {
+export const contact: APIHandler<'contact'> = async ({content, userId}, _auth) => {
const pg = createSupabaseDirectClient()
const {error} = await tryCatch(
insert(pg, 'contact', {
user_id: userId,
content: JSON.stringify(content),
- })
+ }),
)
if (error) throw new APIError(500, 'Failed to submit contact message')
diff --git a/backend/api/src/create-bookmarked-search.ts b/backend/api/src/create-bookmarked-search.ts
index 2dc0bd75..ea753710 100644
--- a/backend/api/src/create-bookmarked-search.ts
+++ b/backend/api/src/create-bookmarked-search.ts
@@ -1,9 +1,10 @@
-import {APIHandler} from './helpers/endpoint'
import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {APIHandler} from './helpers/endpoint'
+
export const createBookmarkedSearch: APIHandler<'create-bookmarked-search'> = async (
props,
- auth
+ auth,
) => {
const creator_id = auth.uid
const {search_filters, location = null, search_name = null} = props
@@ -16,7 +17,7 @@ export const createBookmarkedSearch: APIHandler<'create-bookmarked-search'> = as
VALUES ($1, $2, $3, $4)
RETURNING *
`,
- [creator_id, search_filters, location, search_name]
+ [creator_id, search_filters, location, search_name],
)
return inserted
diff --git a/backend/api/src/create-comment.ts b/backend/api/src/create-comment.ts
index 4fcc887a..e28bf01c 100644
--- a/backend/api/src/create-comment.ts
+++ b/backend/api/src/create-comment.ts
@@ -1,32 +1,25 @@
-import { APIError, APIHandler } from 'api/helpers/endpoint'
-import { type JSONContent } from '@tiptap/core'
-import { getPrivateUser, getUser } from 'shared/utils'
-import {
- createSupabaseDirectClient,
- SupabaseDirectClient,
-} from 'shared/supabase/init'
-import { getNotificationDestinationsForUser } from 'common/user-notification-preferences'
-import { Notification } from 'common/notifications'
-import { insertNotificationToSupabase } from 'shared/supabase/notifications'
-import { User } from 'common/user'
-import { richTextToString } from 'common/util/parse'
+import {type JSONContent} from '@tiptap/core'
+import {APIError, APIHandler} from 'api/helpers/endpoint'
+import {Notification} from 'common/notifications'
+import {convertComment} from 'common/supabase/comment'
+import {type Row} from 'common/supabase/utils'
+import {User} from 'common/user'
+import {getNotificationDestinationsForUser} from 'common/user-notification-preferences'
+import {richTextToString} from 'common/util/parse'
import * as crypto from 'crypto'
-import { sendNewEndorsementEmail } from 'email/functions/helpers'
-import { type Row } from 'common/supabase/utils'
-import { broadcastUpdatedComment } from 'shared/websockets/helpers'
-import { convertComment } from 'common/supabase/comment'
+import {sendNewEndorsementEmail} from 'email/functions/helpers'
+import {createSupabaseDirectClient, SupabaseDirectClient} from 'shared/supabase/init'
+import {insertNotificationToSupabase} from 'shared/supabase/notifications'
+import {getPrivateUser, getUser} from 'shared/utils'
+import {broadcastUpdatedComment} from 'shared/websockets/helpers'
export const MAX_COMMENT_JSON_LENGTH = 20000
export const createComment: APIHandler<'create-comment'> = async (
- { userId, content: submittedContent, replyToCommentId },
- auth
+ {userId, content: submittedContent, replyToCommentId},
+ auth,
) => {
- const { creator, content } = await validateComment(
- userId,
- auth.uid,
- submittedContent
- )
+ const {creator, content} = await validateComment(userId, auth.uid, submittedContent)
const onUser = await getUser(userId)
if (!onUser) throw new APIError(404, 'User not found')
@@ -43,7 +36,7 @@ export const createComment: APIHandler<'create-comment'> = async (
userId,
content,
replyToCommentId,
- ]
+ ],
)
if (onUser.id !== creator.id)
await createNewCommentOnProfileNotification(
@@ -51,19 +44,15 @@ export const createComment: APIHandler<'create-comment'> = async (
creator,
richTextToString(content),
comment.id,
- pg
+ pg,
)
broadcastUpdatedComment(convertComment(comment))
- return { status: 'success' }
+ return {status: 'success'}
}
-const validateComment = async (
- userId: string,
- creatorId: string,
- content: JSONContent
-) => {
+const validateComment = async (userId: string, creatorId: string, content: JSONContent) => {
const creator = await getUser(creatorId)
if (!creator) throw new APIError(401, 'Your account was not found')
@@ -78,10 +67,10 @@ const validateComment = async (
if (JSON.stringify(content).length > MAX_COMMENT_JSON_LENGTH) {
throw new APIError(
400,
- `Comment is too long; should be less than ${MAX_COMMENT_JSON_LENGTH} as a JSON string.`
+ `Comment is too long; should be less than ${MAX_COMMENT_JSON_LENGTH} as a JSON string.`,
)
}
- return { content, creator }
+ return {content, creator}
}
const createNewCommentOnProfileNotification = async (
@@ -89,14 +78,16 @@ const createNewCommentOnProfileNotification = async (
creator: User,
sourceText: string,
commentId: number,
- pg: SupabaseDirectClient
+ pg: SupabaseDirectClient,
) => {
const privateUser = await getPrivateUser(onUser.id)
if (!privateUser) return
const id = crypto.randomUUID()
const reason = 'new_endorsement'
- const { sendToBrowser, sendToMobile, sendToEmail } =
- getNotificationDestinationsForUser(privateUser, reason)
+ const {sendToBrowser, sendToMobile, sendToEmail} = getNotificationDestinationsForUser(
+ privateUser,
+ reason,
+ )
const notification: Notification = {
id,
userId: privateUser.id,
diff --git a/backend/api/src/create-compatibility-question.ts b/backend/api/src/create-compatibility-question.ts
index 8483e943..81bcf1be 100644
--- a/backend/api/src/create-compatibility-question.ts
+++ b/backend/api/src/create-compatibility-question.ts
@@ -1,27 +1,29 @@
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { getUser } from 'shared/utils'
-import { APIHandler, APIError } from './helpers/endpoint'
-import { insert } from 'shared/supabase/utils'
-import { tryCatch } from 'common/util/try-catch'
+import {tryCatch} from 'common/util/try-catch'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {insert} from 'shared/supabase/utils'
+import {getUser} from 'shared/utils'
-export const createCompatibilityQuestion: APIHandler<
- 'create-compatibility-question'
-> = async ({ question, options }, auth) => {
+import {APIError, APIHandler} from './helpers/endpoint'
+
+export const createCompatibilityQuestion: APIHandler<'create-compatibility-question'> = async (
+ {question, options},
+ auth,
+) => {
const creator = await getUser(auth.uid)
if (!creator) throw new APIError(401, 'Your account was not found')
const pg = createSupabaseDirectClient()
- const { data, error } = await tryCatch(
+ const {data, error} = await tryCatch(
insert(pg, 'compatibility_prompts', {
creator_id: creator.id,
question,
answer_type: 'compatibility_multiple_choice',
multiple_choice_options: options,
- })
+ }),
)
if (error) throw new APIError(401, 'Error creating question')
- return { question: data }
+ return {question: data}
}
diff --git a/backend/api/src/create-event.ts b/backend/api/src/create-event.ts
index 982503b0..8bf21f02 100644
--- a/backend/api/src/create-event.ts
+++ b/backend/api/src/create-event.ts
@@ -1,7 +1,7 @@
import {APIError, APIHandler} from 'api/helpers/endpoint'
+import {tryCatch} from 'common/util/try-catch'
import {createSupabaseDirectClient} from 'shared/supabase/init'
import {insert} from 'shared/supabase/utils'
-import {tryCatch} from 'common/util/try-catch'
export const createEvent: APIHandler<'create-event'> = async (body, auth) => {
const pg = createSupabaseDirectClient()
@@ -38,7 +38,7 @@ export const createEvent: APIHandler<'create-event'> = async (body, auth) => {
event_start_time: body.eventStartTime,
event_end_time: body.eventEndTime,
max_participants: body.maxParticipants,
- })
+ }),
)
if (error) {
diff --git a/backend/api/src/create-notification.ts b/backend/api/src/create-notification.ts
index 55a7cfdd..0c960313 100644
--- a/backend/api/src/create-notification.ts
+++ b/backend/api/src/create-notification.ts
@@ -1,9 +1,9 @@
-import {createSupabaseDirectClient, SupabaseDirectClient,} from 'shared/supabase/init'
-import {Notification} from 'common/notifications'
-import {createBulkNotification, insertNotificationToSupabase,} from 'shared/supabase/notifications'
-import {tryCatch} from 'common/util/try-catch'
-import {Row} from 'common/supabase/utils'
import {ANDROID_APP_URL} from 'common/constants'
+import {Notification} from 'common/notifications'
+import {Row} from 'common/supabase/utils'
+import {tryCatch} from 'common/util/try-catch'
+import {createSupabaseDirectClient, SupabaseDirectClient} from 'shared/supabase/init'
+import {createBulkNotification, insertNotificationToSupabase} from 'shared/supabase/notifications'
export const createAndroidReleaseNotifications = async () => {
const createdTime = Date.now()
@@ -38,8 +38,7 @@ export const createAndroidTestNotifications = async () => {
sourceSlug: '/contact',
sourceUserAvatarUrl:
'https://firebasestorage.googleapis.com/v0/b/compass-130ba.firebasestorage.app/o/misc%2Fcompass-192.png?alt=media&token=9fd251c5-fc43-4375-b629-1a8f4bbe8185',
- title:
- 'Android App Ready for Review — Help Us Unlock the Google Play Release',
+ title: 'Android App Ready for Review — Help Us Unlock the Google Play Release',
sourceText:
'To release our app, Google requires a closed test with at least 12 testers for 14 days. Please share your Google Play–registered email address so we can add you as a tester and complete the review process.',
}
@@ -60,8 +59,7 @@ export const createShareNotifications = async () => {
sourceUserAvatarUrl:
'https://firebasestorage.googleapis.com/v0/b/compass-130ba.firebasestorage.app/o/misc%2Ficon-outreach-outstrip-outreach-272151502.jpg?alt=media&token=6d6fcecb-818c-4fca-a8e0-d2d0069b9445',
title: 'Give us tips to reach more people',
- sourceText:
- '250 members already! Tell us where and how we can best share Compass.',
+ sourceText: '250 members already! Tell us where and how we can best share Compass.',
}
return await createNotifications(notification)
}
@@ -87,9 +85,7 @@ export const createVoteNotifications = async () => {
export const createNotifications = async (notification: Notification) => {
const pg = createSupabaseDirectClient()
- const {data: users, error} = await tryCatch(
- pg.many>('select * from users')
- )
+ const {data: users, error} = await tryCatch(pg.many>('select * from users'))
if (error) {
console.error('Error fetching users', error)
@@ -117,7 +113,7 @@ export const createNotifications = async (notification: Notification) => {
export const createNotification = async (
user: Row<'users'>,
notification: Notification,
- pg: SupabaseDirectClient
+ pg: SupabaseDirectClient,
) => {
notification.userId = user.id
console.log('notification', user.username)
@@ -132,9 +128,7 @@ export const createEventsAvailableNotifications = async () => {
const pg = createSupabaseDirectClient()
// Fetch all users
- const {data: users, error} = await tryCatch(
- pg.many>('select id from users')
- )
+ const {data: users, error} = await tryCatch(pg.many>('select id from users'))
if (error) {
console.error('Error fetching users', error)
@@ -161,12 +155,10 @@ export const createEventsAvailableNotifications = async () => {
sourceUpdateType: 'created',
},
userIds,
- pg
+ pg,
)
- console.log(
- `Created events notification template ${templateId} for ${count} users`
- )
+ console.log(`Created events notification template ${templateId} for ${count} users`)
return {
success: true,
diff --git a/backend/api/src/create-private-user-message-channel.ts b/backend/api/src/create-private-user-message-channel.ts
index fe0fd92e..a5687642 100644
--- a/backend/api/src/create-private-user-message-channel.ts
+++ b/backend/api/src/create-private-user-message-channel.ts
@@ -1,10 +1,10 @@
import {APIError, APIHandler} from 'api/helpers/endpoint'
+import {addUsersToPrivateMessageChannel} from 'api/helpers/private-messages'
import {filterDefined} from 'common/util/array'
+import * as admin from 'firebase-admin'
import {uniq} from 'lodash'
import {createSupabaseDirectClient} from 'shared/supabase/init'
-import {addUsersToPrivateMessageChannel} from 'api/helpers/private-messages'
import {getPrivateUser, getUser} from 'shared/utils'
-import * as admin from 'firebase-admin'
export const createPrivateUserMessageChannel: APIHandler<
'create-private-user-message-channel'
@@ -13,10 +13,7 @@ export const createPrivateUserMessageChannel: APIHandler<
const user = await admin.auth().getUser(auth.uid)
// console.log(JSON.stringify(user, null, 2))
if (!user?.emailVerified) {
- throw new APIError(
- 403,
- 'You must verify your email to contact people.'
- )
+ throw new APIError(403, 'You must verify your email to contact people.')
}
const userIds = uniq(body.userIds.concat(auth.uid))
@@ -27,27 +24,22 @@ export const createPrivateUserMessageChannel: APIHandler<
const creator = await getUser(creatorId)
if (!creator) throw new APIError(401, 'Your account was not found')
if (creator.isBannedFromPosting) throw new APIError(403, 'You are banned')
- const toPrivateUsers = filterDefined(
- await Promise.all(userIds.map((id) => getPrivateUser(id)))
- )
+ const toPrivateUsers = filterDefined(await Promise.all(userIds.map((id) => getPrivateUser(id))))
if (toPrivateUsers.length !== userIds.length)
throw new APIError(
404,
`Private user ${userIds.find(
- (uid) => !toPrivateUsers.map((p) => p.id).includes(uid)
- )} not found`
+ (uid) => !toPrivateUsers.map((p) => p.id).includes(uid),
+ )} not found`,
)
if (
toPrivateUsers.some((user) =>
- user.blockedUserIds.some((blockedId) => userIds.includes(blockedId))
+ user.blockedUserIds.some((blockedId) => userIds.includes(blockedId)),
)
) {
- throw new APIError(
- 403,
- 'One of the users has blocked another user in the list'
- )
+ throw new APIError(403, 'One of the users has blocked another user in the list')
}
const currentChannel = await pg.oneOrNone(
@@ -58,7 +50,7 @@ export const createPrivateUserMessageChannel: APIHandler<
having array_agg(user_id::text) @> array [$1]::text[]
and array_agg(user_id::text) <@ array [$1]::text[]
`,
- [userIds]
+ [userIds],
)
if (currentChannel)
return {
@@ -69,14 +61,14 @@ export const createPrivateUserMessageChannel: APIHandler<
const channel = await pg.one(
`insert into private_user_message_channels default
values
- returning id`
+ returning id`,
)
await pg.none(
`insert into private_user_message_channel_members (channel_id, user_id, role, status)
values ($1, $2, 'creator', 'joined')
`,
- [channel.id, creatorId]
+ [channel.id, creatorId],
)
const memberIds = userIds.filter((id) => id !== creatorId)
diff --git a/backend/api/src/create-private-user-message.ts b/backend/api/src/create-private-user-message.ts
index 1759b477..b02780f9 100644
--- a/backend/api/src/create-private-user-message.ts
+++ b/backend/api/src/create-private-user-message.ts
@@ -1,18 +1,16 @@
-import {APIError, APIHandler} from 'api/helpers/endpoint'
-import {getUser} from 'shared/utils'
-import {createSupabaseDirectClient} from 'shared/supabase/init'
import {MAX_COMMENT_JSON_LENGTH} from 'api/create-comment'
+import {APIError, APIHandler} from 'api/helpers/endpoint'
import {createPrivateUserMessageMain} from 'api/helpers/private-messages'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {getUser} from 'shared/utils'
-export const createPrivateUserMessage: APIHandler<
- 'create-private-user-message'
-> = async (body, auth) => {
+export const createPrivateUserMessage: APIHandler<'create-private-user-message'> = async (
+ body,
+ auth,
+) => {
const {content, channelId} = body
if (JSON.stringify(content).length > MAX_COMMENT_JSON_LENGTH) {
- throw new APIError(
- 400,
- `Message JSON should be less than ${MAX_COMMENT_JSON_LENGTH}`
- )
+ throw new APIError(400, `Message JSON should be less than ${MAX_COMMENT_JSON_LENGTH}`)
}
const creator = await getUser(auth.uid)
@@ -20,11 +18,5 @@ export const createPrivateUserMessage: APIHandler<
if (creator.isBannedFromPosting) throw new APIError(403, 'You are banned')
const pg = createSupabaseDirectClient()
- return await createPrivateUserMessageMain(
- creator,
- channelId,
- content,
- pg,
- 'private'
- )
+ return await createPrivateUserMessageMain(creator, channelId, content, pg, 'private')
}
diff --git a/backend/api/src/create-profile.ts b/backend/api/src/create-profile.ts
index b9efb837..c13ec6bf 100644
--- a/backend/api/src/create-profile.ts
+++ b/backend/api/src/create-profile.ts
@@ -1,23 +1,21 @@
import {APIError, APIHandler} from 'api/helpers/endpoint'
-import {createSupabaseDirectClient} from 'shared/supabase/init'
-import {getUser, log} from 'shared/utils'
+import {sendDiscordMessage} from 'common/discord/core'
+import {jsonToMarkdown} from 'common/md'
+import {trimStrings} from 'common/parsing'
import {HOUR_MS, MINUTE_MS, sleep} from 'common/util/time'
-import {removePinnedUrlFromPhotoUrls} from 'shared/profiles/parse-photos'
-import {track} from 'shared/analytics'
-import {updateUser} from 'shared/supabase/users'
import {tryCatch} from 'common/util/try-catch'
+import {track} from 'shared/analytics'
+import {removePinnedUrlFromPhotoUrls} from 'shared/profiles/parse-photos'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {updateUser} from 'shared/supabase/users'
import {insert} from 'shared/supabase/utils'
-import {sendDiscordMessage} from "common/discord/core";
-import {jsonToMarkdown} from "common/md";
-import {trimStrings} from "common/parsing";
+import {getUser, log} from 'shared/utils'
export const createProfile: APIHandler<'create-profile'> = async (body, auth) => {
const pg = createSupabaseDirectClient()
- const { data: existingUser } = await tryCatch(
- pg.oneOrNone<{ id: string }>('select id from profiles where user_id = $1', [
- auth.uid,
- ])
+ const {data: existingUser} = await tryCatch(
+ pg.oneOrNone<{id: string}>('select id from profiles where user_id = $1', [auth.uid]),
)
if (existingUser) {
throw new APIError(400, 'User already exists')
@@ -30,14 +28,12 @@ export const createProfile: APIHandler<'create-profile'> = async (body, auth) =>
if (!user) throw new APIError(401, 'Your account was not found')
if (user.createdTime > Date.now() - HOUR_MS) {
// If they just signed up, set their avatar to be their pinned photo
- updateUser(pg, auth.uid, { avatarUrl: body.pinned_url })
+ updateUser(pg, auth.uid, {avatarUrl: body.pinned_url})
}
console.debug('body', body)
- const { data, error } = await tryCatch(
- insert(pg, 'profiles', { user_id: auth.uid, ...body })
- )
+ const {data, error} = await tryCatch(insert(pg, 'profiles', {user_id: auth.uid, ...body}))
if (error) {
log.error('Error creating user: ' + error.message)
@@ -67,10 +63,8 @@ export const createProfile: APIHandler<'create-profile'> = async (body, auth) =>
console.error('Failed to send discord new profile', e)
}
try {
- const nProfiles = await pg.one(
- `SELECT count(*) FROM profiles`,
- [],
- (r) => Number(r.count)
+ const nProfiles = await pg.one(`SELECT count(*) FROM profiles`, [], (r) =>
+ Number(r.count),
)
const isMilestone = (n: number) => {
@@ -81,12 +75,8 @@ export const createProfile: APIHandler<'create-profile'> = async (body, auth) =>
}
console.debug(nProfiles, isMilestone(nProfiles))
if (isMilestone(nProfiles)) {
- await sendDiscordMessage(
- `We just reached **${nProfiles}** total profiles! 🎉`,
- 'general',
- )
+ await sendDiscordMessage(`We just reached **${nProfiles}** total profiles! 🎉`, 'general')
}
-
} catch (e) {
console.error('Failed to send discord user milestone', e)
}
diff --git a/backend/api/src/create-user.ts b/backend/api/src/create-user.ts
index d4dd8541..082577b8 100644
--- a/backend/api/src/create-user.ts
+++ b/backend/api/src/create-user.ts
@@ -1,32 +1,28 @@
-import * as admin from 'firebase-admin'
-import {PrivateUser} from 'common/user'
-import {randomString} from 'common/util/random'
-import {cleanDisplayName, cleanUsername} from 'common/util/clean-username'
-import {getIp, track} from 'shared/analytics'
-import {APIError, APIHandler} from './helpers/endpoint'
-import {getDefaultNotificationPreferences} from 'common/user-notification-preferences'
-import {removeUndefinedProps} from 'common/util/object'
-import {generateAvatarUrl} from 'shared/helpers/generate-and-update-avatar-urls'
+import {setLastOnlineTimeUser} from 'api/set-last-online-time'
import {RESERVED_PATHS} from 'common/envs/constants'
-import {getUser, getUserByUsername, log} from 'shared/utils'
+import {IS_LOCAL} from 'common/hosting/constants'
+import {convertPrivateUser, convertUser} from 'common/supabase/users'
+import {PrivateUser} from 'common/user'
+import {getDefaultNotificationPreferences} from 'common/user-notification-preferences'
+import {cleanDisplayName, cleanUsername} from 'common/util/clean-username'
+import {removeUndefinedProps} from 'common/util/object'
+import {randomString} from 'common/util/random'
+import {sendWelcomeEmail} from 'email/functions/helpers'
+import * as admin from 'firebase-admin'
+import {getIp, track} from 'shared/analytics'
+import {getBucket} from 'shared/firebase-utils'
+import {generateAvatarUrl} from 'shared/helpers/generate-and-update-avatar-urls'
import {createSupabaseDirectClient} from 'shared/supabase/init'
import {insert} from 'shared/supabase/utils'
-import {convertPrivateUser, convertUser} from 'common/supabase/users'
-import {getBucket} from "shared/firebase-utils";
-import {sendWelcomeEmail} from "email/functions/helpers";
-import {setLastOnlineTimeUser} from "api/set-last-online-time";
-import {IS_LOCAL} from "common/hosting/constants";
+import {getUser, getUserByUsername, log} from 'shared/utils'
-export const createUser: APIHandler<'create-user'> = async (
- props,
- auth,
- req
-) => {
+import {APIError, APIHandler} from './helpers/endpoint'
+
+export const createUser: APIHandler<'create-user'> = async (props, auth, req) => {
const {deviceToken: preDeviceToken} = props
const firebaseUser = await admin.auth().getUser(auth.uid)
- const testUserAKAEmailPasswordUser =
- firebaseUser.providerData[0].providerId === 'password'
+ const testUserAKAEmailPasswordUser = firebaseUser.providerData[0].providerId === 'password'
// if (
// testUserAKAEmailPasswordUser &&
@@ -68,7 +64,7 @@ export const createUser: APIHandler<'create-user'> = async (
from users
where username ilike $1`,
[username],
- (r) => r.count
+ (r) => r.count,
)
const usernameExists = dupes > 0
const isReservedName = RESERVED_PATHS.includes(username)
@@ -83,14 +79,13 @@ export const createUser: APIHandler<'create-user'> = async (
// Check exact username to avoid problems with duplicate requests
const sameNameUser = await getUserByUsername(username, tx)
- if (sameNameUser)
- throw new APIError(403, 'Username already taken', {username})
+ if (sameNameUser) throw new APIError(403, 'Username already taken', {username})
const user = removeUndefinedProps({
avatarUrl,
isBannedFromPosting: Boolean(
(deviceToken && bannedDeviceTokens.includes(deviceToken)) ||
- (ip && bannedIpAddresses.includes(ip))
+ (ip && bannedIpAddresses.includes(ip)),
),
link: {},
})
diff --git a/backend/api/src/create-vote.ts b/backend/api/src/create-vote.ts
index ffddee59..7b15deea 100644
--- a/backend/api/src/create-vote.ts
+++ b/backend/api/src/create-vote.ts
@@ -1,28 +1,30 @@
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { getUser } from 'shared/utils'
-import { APIHandler, APIError } from './helpers/endpoint'
-import { insert } from 'shared/supabase/utils'
-import { tryCatch } from 'common/util/try-catch'
+import {tryCatch} from 'common/util/try-catch'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {insert} from 'shared/supabase/utils'
+import {getUser} from 'shared/utils'
-export const createVote: APIHandler<
- 'create-vote'
-> = async ({ title, description, isAnonymous }, auth) => {
+import {APIError, APIHandler} from './helpers/endpoint'
+
+export const createVote: APIHandler<'create-vote'> = async (
+ {title, description, isAnonymous},
+ auth,
+) => {
const creator = await getUser(auth.uid)
if (!creator) throw new APIError(401, 'Your account was not found')
const pg = createSupabaseDirectClient()
- const { data, error } = await tryCatch(
+ const {data, error} = await tryCatch(
insert(pg, 'votes', {
creator_id: creator.id,
title,
description,
is_anonymous: isAnonymous,
status: 'voting_open',
- })
+ }),
)
if (error) throw new APIError(401, 'Error creating question')
- return { data }
+ return {data}
}
diff --git a/backend/api/src/delete-bookmarked-search.ts b/backend/api/src/delete-bookmarked-search.ts
index 2326c3f3..9b5b95ca 100644
--- a/backend/api/src/delete-bookmarked-search.ts
+++ b/backend/api/src/delete-bookmarked-search.ts
@@ -1,9 +1,10 @@
-import {APIHandler} from './helpers/endpoint'
import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {APIHandler} from './helpers/endpoint'
+
export const deleteBookmarkedSearch: APIHandler<'delete-bookmarked-search'> = async (
props,
- auth
+ auth,
) => {
const creator_id = auth.uid
const {id} = props
@@ -16,7 +17,7 @@ export const deleteBookmarkedSearch: APIHandler<'delete-bookmarked-search'> = as
DELETE FROM bookmarked_searches
WHERE id = $1 AND creator_id = $2
`,
- [id, creator_id]
+ [id, creator_id],
)
return {}
diff --git a/backend/api/src/delete-compatibility-answer.ts b/backend/api/src/delete-compatibility-answer.ts
index 142594cb..3e09e8cc 100644
--- a/backend/api/src/delete-compatibility-answer.ts
+++ b/backend/api/src/delete-compatibility-answer.ts
@@ -1,10 +1,12 @@
import {APIHandler} from 'api/helpers/endpoint'
-import {createSupabaseDirectClient} from 'shared/supabase/init'
import {APIError} from 'common/api/utils'
import {recomputeCompatibilityScoresForUser} from 'shared/compatibility/compute-scores'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
export const deleteCompatibilityAnswer: APIHandler<'delete-compatibility-answer'> = async (
- {id}, auth) => {
+ {id},
+ auth,
+) => {
const pg = createSupabaseDirectClient()
// Verify user is the answer author
@@ -13,7 +15,7 @@ export const deleteCompatibilityAnswer: APIHandler<'delete-compatibility-answer'
FROM compatibility_answers
WHERE id = $1
AND creator_id = $2`,
- [id, auth.uid]
+ [id, auth.uid],
)
if (!item) {
@@ -26,7 +28,7 @@ export const deleteCompatibilityAnswer: APIHandler<'delete-compatibility-answer'
FROM compatibility_answers
WHERE id = $1
AND creator_id = $2`,
- [id, auth.uid]
+ [id, auth.uid],
)
const continuation = async () => {
diff --git a/backend/api/src/delete-me.ts b/backend/api/src/delete-me.ts
index 12af1f69..17ccdec7 100644
--- a/backend/api/src/delete-me.ts
+++ b/backend/api/src/delete-me.ts
@@ -1,8 +1,9 @@
-import {getUser} from 'shared/utils'
-import {APIError, APIHandler} from './helpers/endpoint'
+import * as admin from 'firebase-admin'
+import {deleteUserFiles} from 'shared/firebase-utils'
import {createSupabaseDirectClient} from 'shared/supabase/init'
-import * as admin from "firebase-admin";
-import {deleteUserFiles} from "shared/firebase-utils";
+import {getUser} from 'shared/utils'
+
+import {APIError, APIHandler} from './helpers/endpoint'
export const deleteMe: APIHandler<'me/delete'> = async (_, auth) => {
const user = await getUser(auth.uid)
diff --git a/backend/api/src/delete-message.ts b/backend/api/src/delete-message.ts
index 25151850..f90da5fc 100644
--- a/backend/api/src/delete-message.ts
+++ b/backend/api/src/delete-message.ts
@@ -1,6 +1,7 @@
-import {APIError, APIHandler} from './helpers/endpoint'
+import {broadcastPrivateMessages} from 'api/helpers/private-messages'
import {createSupabaseDirectClient} from 'shared/supabase/init'
-import {broadcastPrivateMessages} from "api/helpers/private-messages";
+
+import {APIError, APIHandler} from './helpers/endpoint'
// const DELETED_MESSAGE_CONTENT: JSONContent = {
// type: 'doc',
@@ -26,7 +27,7 @@ export const deleteMessage: APIHandler<'delete-message'> = async ({messageId}, a
FROM private_user_messages
WHERE id = $1
AND user_id = $2`,
- [messageId, auth.uid]
+ [messageId, auth.uid],
)
if (!message) {
@@ -51,14 +52,12 @@ export const deleteMessage: APIHandler<'delete-message'> = async ({messageId}, a
FROM private_user_messages
WHERE id = $1
AND user_id = $2`,
- [messageId, auth.uid]
+ [messageId, auth.uid],
)
- void broadcastPrivateMessages(pg, message.channel_id, auth.uid)
- .catch((err) => {
- console.error('broadcastPrivateMessages failed', err)
- })
+ void broadcastPrivateMessages(pg, message.channel_id, auth.uid).catch((err) => {
+ console.error('broadcastPrivateMessages failed', err)
+ })
return {success: true}
}
-
diff --git a/backend/api/src/edit-message.ts b/backend/api/src/edit-message.ts
index aba2f11b..522e4ffd 100644
--- a/backend/api/src/edit-message.ts
+++ b/backend/api/src/edit-message.ts
@@ -1,8 +1,8 @@
-import {APIError, APIHandler} from './helpers/endpoint'
+import {broadcastPrivateMessages} from 'api/helpers/private-messages'
+import {encryptMessage} from 'shared/encryption'
import {createSupabaseDirectClient} from 'shared/supabase/init'
-import {encryptMessage} from "shared/encryption";
-import {broadcastPrivateMessages} from "api/helpers/private-messages";
+import {APIError, APIHandler} from './helpers/endpoint'
export const editMessage: APIHandler<'edit-message'> = async ({messageId, content}, auth) => {
const pg = createSupabaseDirectClient()
@@ -15,7 +15,7 @@ export const editMessage: APIHandler<'edit-message'> = async ({messageId, conten
AND user_id = $2
-- AND created_time > NOW() - INTERVAL '1 day'
AND deleted = FALSE`,
- [messageId, auth.uid]
+ [messageId, auth.uid],
)
if (!message) {
@@ -32,13 +32,12 @@ export const editMessage: APIHandler<'edit-message'> = async ({messageId, conten
is_edited = TRUE,
edited_at = NOW()
WHERE id = $4`,
- [ciphertext, iv, tag, messageId]
+ [ciphertext, iv, tag, messageId],
)
- void broadcastPrivateMessages(pg, message.channel_id, auth.uid)
- .catch((err) => {
- console.error('broadcastPrivateMessages failed', err)
- })
+ void broadcastPrivateMessages(pg, message.channel_id, auth.uid).catch((err) => {
+ console.error('broadcastPrivateMessages failed', err)
+ })
return {success: true}
}
diff --git a/backend/api/src/get-compatibililty-questions.ts b/backend/api/src/get-compatibililty-questions.ts
index 211de7ba..bbcec3d1 100644
--- a/backend/api/src/get-compatibililty-questions.ts
+++ b/backend/api/src/get-compatibililty-questions.ts
@@ -1,6 +1,6 @@
import {type APIHandler} from 'api/helpers/endpoint'
-import {createSupabaseDirectClient} from 'shared/supabase/init'
import {Row} from 'common/supabase/utils'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
export function shuffle(array: T[]): T[] {
const arr = [...array] // copy to avoid mutating the original
@@ -11,9 +11,10 @@ export function shuffle(array: T[]): T[] {
return arr
}
-export const getCompatibilityQuestions: APIHandler<
- 'get-compatibility-questions'
-> = async (props, _auth) => {
+export const getCompatibilityQuestions: APIHandler<'get-compatibility-questions'> = async (
+ props,
+ _auth,
+) => {
const {locale = 'en', keyword} = props
const pg = createSupabaseDirectClient()
@@ -40,7 +41,7 @@ export const getCompatibilityQuestions: APIHandler<
}
const questions = await pg.manyOrNone<
- Row<'compatibility_prompts'> & { answer_count: number; score: number }
+ Row<'compatibility_prompts'> & {answer_count: number; score: number}
>(
`
SELECT cp.id,
@@ -82,7 +83,7 @@ export const getCompatibilityQuestions: APIHandler<
ORDER BY cp.importance_score
`,
- params
+ params,
)
// console.debug({questions})
diff --git a/backend/api/src/get-current-private-user.ts b/backend/api/src/get-current-private-user.ts
index 296aedfa..6d1cb9f1 100644
--- a/backend/api/src/get-current-private-user.ts
+++ b/backend/api/src/get-current-private-user.ts
@@ -1,27 +1,19 @@
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { APIError, APIHandler } from './helpers/endpoint'
-import { PrivateUser } from 'common/user'
-import { Row } from 'common/supabase/utils'
-import { tryCatch } from 'common/util/try-catch'
+import {Row} from 'common/supabase/utils'
+import {PrivateUser} from 'common/user'
+import {tryCatch} from 'common/util/try-catch'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
-export const getCurrentPrivateUser: APIHandler<'me/private'> = async (
- _,
- auth
-) => {
+import {APIError, APIHandler} from './helpers/endpoint'
+
+export const getCurrentPrivateUser: APIHandler<'me/private'> = async (_, auth) => {
const pg = createSupabaseDirectClient()
- const { data, error } = await tryCatch(
- pg.oneOrNone>(
- 'select * from private_users where id = $1',
- [auth.uid]
- )
+ const {data, error} = await tryCatch(
+ pg.oneOrNone>('select * from private_users where id = $1', [auth.uid]),
)
if (error) {
- throw new APIError(
- 500,
- 'Error fetching private user data: ' + error.message
- )
+ throw new APIError(500, 'Error fetching private user data: ' + error.message)
}
if (!data) {
diff --git a/backend/api/src/get-events.ts b/backend/api/src/get-events.ts
index 31f4c720..326f1c6a 100644
--- a/backend/api/src/get-events.ts
+++ b/backend/api/src/get-events.ts
@@ -23,51 +23,53 @@ export const getEvents: APIHandler<'get-events'> = async () => {
FROM events
WHERE is_public = true
AND status = 'active'
- ORDER BY event_start_time`
+ ORDER BY event_start_time`,
)
// Get participants for each event
- const eventIds = events.map(e => e.id)
- const participants = eventIds.length > 0
- ? await pg.manyOrNone<{
- event_id: string
- user_id: string
- status: 'going' | 'maybe' | 'not_going'
- }>(
- `SELECT event_id, user_id, status
+ const eventIds = events.map((e) => e.id)
+ const participants =
+ eventIds.length > 0
+ ? await pg.manyOrNone<{
+ event_id: string
+ user_id: string
+ status: 'going' | 'maybe' | 'not_going'
+ }>(
+ `SELECT event_id, user_id, status
FROM events_participants
WHERE event_id = ANY ($1)`,
- [eventIds]
- )
- : []
+ [eventIds],
+ )
+ : []
// Get creator info for each event
- const creatorIds = [...new Set(events.map(e => e.creator_id))]
- const creators = creatorIds.length > 0
- ? await pg.manyOrNone<{
- id: string
- name: string
- username: string
- avatar_url: string | null
- }>(
- `SELECT id, name, username, data ->> 'avatarUrl' as avatar_url
+ const creatorIds = [...new Set(events.map((e) => e.creator_id))]
+ const creators =
+ creatorIds.length > 0
+ ? await pg.manyOrNone<{
+ id: string
+ name: string
+ username: string
+ avatar_url: string | null
+ }>(
+ `SELECT id, name, username, data ->> 'avatarUrl' as avatar_url
FROM users
WHERE id = ANY ($1)`,
- [creatorIds]
- )
- : []
+ [creatorIds],
+ )
+ : []
const now = new Date()
- const eventsWithDetails = events.map(event => ({
+ const eventsWithDetails = events.map((event) => ({
...event,
participants: participants
- .filter(p => p.event_id === event.id && p.status === 'going')
- .map(p => p.user_id),
+ .filter((p) => p.event_id === event.id && p.status === 'going')
+ .map((p) => p.user_id),
maybe: participants
- .filter(p => p.event_id === event.id && p.status === 'maybe')
- .map(p => p.user_id),
- creator: creators.find(c => c.id === event.creator_id),
+ .filter((p) => p.event_id === event.id && p.status === 'maybe')
+ .map((p) => p.user_id),
+ creator: creators.find((c) => c.id === event.creator_id),
}))
const upcoming: typeof eventsWithDetails = []
diff --git a/backend/api/src/get-hidden-profiles.ts b/backend/api/src/get-hidden-profiles.ts
index 7ec67c0b..497b4f74 100644
--- a/backend/api/src/get-hidden-profiles.ts
+++ b/backend/api/src/get-hidden-profiles.ts
@@ -3,16 +3,16 @@ import {createSupabaseDirectClient} from 'shared/supabase/init'
export const getHiddenProfiles: APIHandler<'get-hidden-profiles'> = async (
{limit = 100, offset = 0},
- auth
+ auth,
) => {
const pg = createSupabaseDirectClient()
// Count total hidden for pagination info
- const countRes = await pg.one<{ count: string }>(
+ const countRes = await pg.one<{count: string}>(
`select count(*)::text as count
from hidden_profiles
where hider_user_id = $1`,
- [auth.uid]
+ [auth.uid],
)
const count = Number(countRes.count) || 0
@@ -31,7 +31,7 @@ export const getHiddenProfiles: APIHandler<'get-hidden-profiles'> = async (
username: r.username as string,
avatarUrl: r.avatarUrl as string | null | undefined,
createdTime: r.createdTime as string | undefined,
- })
+ }),
)
return {status: 'success', hidden: rows, count}
diff --git a/backend/api/src/get-likes-and-ships.ts b/backend/api/src/get-likes-and-ships.ts
index 4d257125..55cd8aeb 100644
--- a/backend/api/src/get-likes-and-ships.ts
+++ b/backend/api/src/get-likes-and-ships.ts
@@ -1,10 +1,8 @@
-import { type APIHandler } from 'api/helpers/endpoint'
-import { createSupabaseDirectClient } from 'shared/supabase/init'
+import {type APIHandler} from 'api/helpers/endpoint'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
-export const getLikesAndShips: APIHandler<'get-likes-and-ships'> = async (
- props
-) => {
- const { userId } = props
+export const getLikesAndShips: APIHandler<'get-likes-and-ships'> = async (props) => {
+ const {userId} = props
return {
status: 'success',
@@ -34,7 +32,7 @@ export const getLikesAndShipsMain = async (userId: string) => {
(r) => ({
user_id: r.target_id,
created_time: new Date(r.created_time).getTime(),
- })
+ }),
)
const likesReceived = await pg.map<{
@@ -56,7 +54,7 @@ export const getLikesAndShipsMain = async (userId: string) => {
(r) => ({
user_id: r.creator_id,
created_time: new Date(r.created_time).getTime(),
- })
+ }),
)
const ships = await pg.map<{
@@ -95,7 +93,7 @@ export const getLikesAndShipsMain = async (userId: string) => {
(r) => ({
...r,
created_time: new Date(r.created_time).getTime(),
- })
+ }),
)
return {
diff --git a/backend/api/src/get-me.ts b/backend/api/src/get-me.ts
index ab58e57d..30fe1187 100644
--- a/backend/api/src/get-me.ts
+++ b/backend/api/src/get-me.ts
@@ -1,6 +1,7 @@
-import { type APIHandler } from './helpers/endpoint'
-import { getUser } from 'api/get-user'
+import {getUser} from 'api/get-user'
+
+import {type APIHandler} from './helpers/endpoint'
export const getMe: APIHandler<'me'> = async (_, auth) => {
- return getUser({ id: auth.uid })
+ return getUser({id: auth.uid})
}
diff --git a/backend/api/src/get-messages-count.ts b/backend/api/src/get-messages-count.ts
index 9dce65b7..e27f1daa 100644
--- a/backend/api/src/get-messages-count.ts
+++ b/backend/api/src/get-messages-count.ts
@@ -1,5 +1,6 @@
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+
import {APIHandler} from './helpers/endpoint'
-import {createSupabaseDirectClient} from "shared/supabase/init";
export const getMessagesCount: APIHandler<'get-messages-count'> = async (_, _auth) => {
const pg = createSupabaseDirectClient()
@@ -8,10 +9,10 @@ export const getMessagesCount: APIHandler<'get-messages-count'> = async (_, _aut
SELECT COUNT(*) AS count
FROM private_user_messages;
`,
- []
- );
- const count = Number(result.count);
- console.debug('private_user_messages count:', count);
+ [],
+ )
+ const count = Number(result.count)
+ console.debug('private_user_messages count:', count)
return {
count: count,
}
diff --git a/backend/api/src/get-notifications.ts b/backend/api/src/get-notifications.ts
index c2c84ce5..9629aae6 100644
--- a/backend/api/src/get-notifications.ts
+++ b/backend/api/src/get-notifications.ts
@@ -1,12 +1,9 @@
-import {createSupabaseDirectClient} from 'shared/supabase/init'
import {APIHandler} from 'api/helpers/endpoint'
import {Notification} from 'common/notifications'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
-export const getNotifications: APIHandler<'get-notifications'> = async (
- props,
- auth
-) => {
- const { limit, after } = props
+export const getNotifications: APIHandler<'get-notifications'> = async (props, auth) => {
+ const {limit, after} = props
const pg = createSupabaseDirectClient()
const query = `
select case
@@ -47,6 +44,6 @@ export const getNotifications: APIHandler<'get-notifications'> = async (
return await pg.map(
query,
[auth.uid, limit, after],
- (row) => row.notification_data as Notification
+ (row) => row.notification_data as Notification,
)
}
diff --git a/backend/api/src/get-options.ts b/backend/api/src/get-options.ts
index fff028de..0b39e7a1 100644
--- a/backend/api/src/get-options.ts
+++ b/backend/api/src/get-options.ts
@@ -1,20 +1,17 @@
import {APIError, APIHandler} from 'api/helpers/endpoint'
+import {OPTION_TABLES} from 'common/profiles/constants'
+import {tryCatch} from 'common/util/try-catch'
import {createSupabaseDirectClient} from 'shared/supabase/init'
import {log} from 'shared/utils'
-import {tryCatch} from 'common/util/try-catch'
-import {OPTION_TABLES} from "common/profiles/constants";
-export const getOptions: APIHandler<'get-options'> = async (
- {table},
- _auth
-) => {
+export const getOptions: APIHandler<'get-options'> = async ({table}, _auth) => {
if (!OPTION_TABLES.includes(table)) throw new APIError(400, 'Invalid table')
const pg = createSupabaseDirectClient()
const result = await tryCatch(
- pg.manyOrNone<{ name: string }>(`SELECT interests.name
- FROM interests`)
+ pg.manyOrNone<{name: string}>(`SELECT interests.name
+ FROM interests`),
)
if (result.error) {
@@ -22,7 +19,6 @@ export const getOptions: APIHandler<'get-options'> = async (
throw new APIError(500, 'Error getting profile options')
}
- const names = result.data.map(row => row.name)
+ const names = result.data.map((row) => row.name)
return {names}
}
-
diff --git a/backend/api/src/get-private-messages.ts b/backend/api/src/get-private-messages.ts
index b6de9949..604b8686 100644
--- a/backend/api/src/get-private-messages.ts
+++ b/backend/api/src/get-private-messages.ts
@@ -1,13 +1,12 @@
-import {createSupabaseDirectClient} from 'shared/supabase/init'
-import {APIError, APIHandler} from './helpers/endpoint'
-import {PrivateMessageChannel,} from 'common/supabase/private-messages'
+import {PrivateMessageChannel} from 'common/supabase/private-messages'
+import {tryCatch} from 'common/util/try-catch'
import {groupBy, mapValues} from 'lodash'
-import {convertPrivateChatMessage} from "shared/supabase/messages";
-import {tryCatch} from "common/util/try-catch";
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {convertPrivateChatMessage} from 'shared/supabase/messages'
-export const getChannelMemberships: APIHandler<
- 'get-channel-memberships'
-> = async (props, auth) => {
+import {APIError, APIHandler} from './helpers/endpoint'
+
+export const getChannelMemberships: APIHandler<'get-channel-memberships'> = async (props, auth) => {
const pg = createSupabaseDirectClient()
const {channelId, lastUpdatedTime, createdTime, limit} = props
@@ -29,7 +28,7 @@ export const getChannelMemberships: APIHandler<
limit $3
`,
[auth.uid, channelId, limit],
- convertRow
+ convertRow,
)
} else {
channels = await pg.map(
@@ -59,11 +58,10 @@ export const getChannelMemberships: APIHandler<
limit $3
`,
[auth.uid, createdTime ?? null, limit, lastUpdatedTime ?? null],
- convertRow
+ convertRow,
)
}
- if (!channels || channels.length === 0)
- return {channels: [], memberIdsByChannelId: {}}
+ if (!channels || channels.length === 0) return {channels: [], memberIdsByChannelId: {}}
const channelIds = channels.map((c) => c.channel_id)
const members = await pg.map(
@@ -77,12 +75,11 @@ export const getChannelMemberships: APIHandler<
(r) => ({
channel_id: r.channel_id as number,
user_id: r.user_id as string,
- })
+ }),
)
- const memberIdsByChannelId = mapValues(
- groupBy(members, 'channel_id'),
- (members) => members.map((m) => m.user_id)
+ const memberIdsByChannelId = mapValues(groupBy(members, 'channel_id'), (members) =>
+ members.map((m) => m.user_id),
)
return {
@@ -93,23 +90,24 @@ export const getChannelMemberships: APIHandler<
export const getChannelMessagesEndpoint: APIHandler<'get-channel-messages'> = async (
props,
- auth
+ auth,
) => {
const userId = auth.uid
return await getChannelMessages({...props, userId})
}
export async function getChannelMessages(props: {
- channelId: number;
- limit?: number;
- id?: number | undefined;
- userId: string;
+ channelId: number
+ limit?: number
+ id?: number | undefined
+ userId: string
}) {
// console.log('initial message request', props)
const {channelId, limit, id, userId} = props
const pg = createSupabaseDirectClient()
- const {data, error} = await tryCatch(pg.map(
- `select *, created_time as created_time_ts
+ const {data, error} = await tryCatch(
+ pg.map(
+ `select *, created_time as created_time_ts
from private_user_messages
where channel_id = $1
and exists (select 1
@@ -121,9 +119,10 @@ export async function getChannelMessages(props: {
order by created_time desc
${limit ? 'limit $3' : ''}
`,
- [channelId, userId, limit, id],
- convertPrivateChatMessage
- ))
+ [channelId, userId, limit, id],
+ convertPrivateChatMessage,
+ ),
+ )
if (error) {
console.error(error)
throw new APIError(401, 'Error getting messages')
@@ -132,9 +131,7 @@ export async function getChannelMessages(props: {
return data
}
-export const getLastSeenChannelTime: APIHandler<
- 'get-channel-seen-time'
-> = async (props, auth) => {
+export const getLastSeenChannelTime: APIHandler<'get-channel-seen-time'> = async (props, auth) => {
const pg = createSupabaseDirectClient()
const {channelIds} = props
const unseens = await pg.map(
@@ -145,20 +142,18 @@ export const getLastSeenChannelTime: APIHandler<
order by channel_id, created_time desc
`,
[channelIds, auth.uid],
- (r) => [r.channel_id as number, r.created_time as string]
+ (r) => [r.channel_id as number, r.created_time as string],
)
return unseens as [number, string][]
}
-export const setChannelLastSeenTime: APIHandler<
- 'set-channel-seen-time'
-> = async (props, auth) => {
+export const setChannelLastSeenTime: APIHandler<'set-channel-seen-time'> = async (props, auth) => {
const pg = createSupabaseDirectClient()
const {channelId} = props
await pg.none(
`insert into private_user_seen_message_channels (user_id, channel_id)
values ($1, $2)
`,
- [auth.uid, channelId]
+ [auth.uid, channelId],
)
}
diff --git a/backend/api/src/get-profile-answers.ts b/backend/api/src/get-profile-answers.ts
index 7f7dcef9..1eb8ef4d 100644
--- a/backend/api/src/get-profile-answers.ts
+++ b/backend/api/src/get-profile-answers.ts
@@ -1,12 +1,9 @@
-import { type APIHandler } from 'api/helpers/endpoint'
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { Row } from 'common/supabase/utils'
+import {type APIHandler} from 'api/helpers/endpoint'
+import {Row} from 'common/supabase/utils'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
-export const getProfileAnswers: APIHandler<'get-profile-answers'> = async (
- props,
- _auth
-) => {
- const { userId } = props
+export const getProfileAnswers: APIHandler<'get-profile-answers'> = async (props, _auth) => {
+ const {userId} = props
const pg = createSupabaseDirectClient()
const answers = await pg.manyOrNone>(
@@ -15,7 +12,7 @@ export const getProfileAnswers: APIHandler<'get-profile-answers'> = async (
creator_id = $1
order by created_time desc
`,
- [userId]
+ [userId],
)
return {
diff --git a/backend/api/src/get-profiles.ts b/backend/api/src/get-profiles.ts
index f33a03cf..af990e27 100644
--- a/backend/api/src/get-profiles.ts
+++ b/backend/api/src/get-profiles.ts
@@ -1,60 +1,68 @@
import {type APIHandler} from 'api/helpers/endpoint'
+import {OptionTableKey} from 'common/profiles/constants'
+import {compact} from 'lodash'
import {convertRow} from 'shared/profiles/supabase'
import {createSupabaseDirectClient, pgp} from 'shared/supabase/init'
-import {from, join, leftJoin, limit, orderBy, renderSql, select, where,} from 'shared/supabase/sql-builder'
-import {compact} from "lodash";
-import {OptionTableKey} from "common/profiles/constants";
+import {
+ from,
+ join,
+ leftJoin,
+ limit,
+ orderBy,
+ renderSql,
+ select,
+ where,
+} from 'shared/supabase/sql-builder'
export type profileQueryType = {
- limit?: number | undefined,
- after?: string | undefined,
- userId?: string | undefined,
- name?: string | undefined,
- genders?: string[] | undefined,
- education_levels?: string[] | undefined,
- pref_gender?: string[] | undefined,
- pref_age_min?: number | undefined,
- pref_age_max?: number | undefined,
- drinks_min?: number | undefined,
- drinks_max?: number | undefined,
- big5_openness_min?: number | undefined,
- big5_openness_max?: number | undefined,
- big5_conscientiousness_min?: number | undefined,
- big5_conscientiousness_max?: number | undefined,
- big5_extraversion_min?: number | undefined,
- big5_extraversion_max?: number | undefined,
- big5_agreeableness_min?: number | undefined,
- big5_agreeableness_max?: number | undefined,
- big5_neuroticism_min?: number | undefined,
- big5_neuroticism_max?: number | undefined,
- pref_relation_styles?: string[] | undefined,
- pref_romantic_styles?: string[] | undefined,
- diet?: string[] | undefined,
- political_beliefs?: string[] | undefined,
- mbti?: string[] | undefined,
- relationship_status?: string[] | undefined,
- languages?: string[] | undefined,
- religion?: string[] | undefined,
- wants_kids_strength?: number | undefined,
- has_kids?: number | undefined,
- is_smoker?: boolean | undefined,
- shortBio?: boolean | undefined,
- geodbCityIds?: string[] | undefined,
- lat?: number | undefined,
- lon?: number | undefined,
- radius?: number | undefined,
- compatibleWithUserId?: string | undefined,
- skipId?: string | undefined,
- orderBy?: string | undefined,
- lastModificationWithin?: string | undefined,
- locale?: string | undefined,
+ limit?: number | undefined
+ after?: string | undefined
+ userId?: string | undefined
+ name?: string | undefined
+ genders?: string[] | undefined
+ education_levels?: string[] | undefined
+ pref_gender?: string[] | undefined
+ pref_age_min?: number | undefined
+ pref_age_max?: number | undefined
+ drinks_min?: number | undefined
+ drinks_max?: number | undefined
+ big5_openness_min?: number | undefined
+ big5_openness_max?: number | undefined
+ big5_conscientiousness_min?: number | undefined
+ big5_conscientiousness_max?: number | undefined
+ big5_extraversion_min?: number | undefined
+ big5_extraversion_max?: number | undefined
+ big5_agreeableness_min?: number | undefined
+ big5_agreeableness_max?: number | undefined
+ big5_neuroticism_min?: number | undefined
+ big5_neuroticism_max?: number | undefined
+ pref_relation_styles?: string[] | undefined
+ pref_romantic_styles?: string[] | undefined
+ diet?: string[] | undefined
+ political_beliefs?: string[] | undefined
+ mbti?: string[] | undefined
+ relationship_status?: string[] | undefined
+ languages?: string[] | undefined
+ religion?: string[] | undefined
+ wants_kids_strength?: number | undefined
+ has_kids?: number | undefined
+ is_smoker?: boolean | undefined
+ shortBio?: boolean | undefined
+ geodbCityIds?: string[] | undefined
+ lat?: number | undefined
+ lon?: number | undefined
+ radius?: number | undefined
+ compatibleWithUserId?: string | undefined
+ skipId?: string | undefined
+ orderBy?: string | undefined
+ lastModificationWithin?: string | undefined
+ locale?: string | undefined
} & {
[K in OptionTableKey]?: string[] | undefined
}
// const userActivityColumns = ['last_online_time']
-
export const loadProfiles = async (props: profileQueryType) => {
const pg = createSupabaseDirectClient()
console.debug('loadProfiles', props)
@@ -108,7 +116,12 @@ export const loadProfiles = async (props: profileQueryType) => {
const filterLocation = lat && lon && radius
- const keywords = name ? name.split(",").map(q => q.trim()).filter(Boolean) : []
+ const keywords = name
+ ? name
+ .split(',')
+ .map((q) => q.trim())
+ .filter(Boolean)
+ : []
// console.debug('keywords:', keywords)
if (orderByParam === 'compatibility_score' && !compatibleWithUserId) {
@@ -116,11 +129,12 @@ export const loadProfiles = async (props: profileQueryType) => {
throw Error('Incompatible with user ID')
}
- const tablePrefix = orderByParam === 'compatibility_score'
- ? 'compatibility_scores'
- : orderByParam === 'last_online_time'
- ? 'user_activity'
- : 'profiles'
+ const tablePrefix =
+ orderByParam === 'compatibility_score'
+ ? 'compatibility_scores'
+ : orderByParam === 'last_online_time'
+ ? 'user_activity'
+ : 'profiles'
const userActivityJoin = 'user_activity on user_activity.user_id = profiles.user_id'
@@ -144,7 +158,10 @@ export const loadProfiles = async (props: profileQueryType) => {
const causesJoin = getManyToManyJoin('causes')
const workJoin = getManyToManyJoin('work')
- const compatibilityScoreJoin = pgp.as.format(`compatibility_scores cs on (cs.user_id_1 = LEAST(profiles.user_id, $(compatibleWithUserId)) and cs.user_id_2 = GREATEST(profiles.user_id, $(compatibleWithUserId)))`, {compatibleWithUserId})
+ const compatibilityScoreJoin = pgp.as.format(
+ `compatibility_scores cs on (cs.user_id_1 = LEAST(profiles.user_id, $(compatibleWithUserId)) and cs.user_id_2 = GREATEST(profiles.user_id, $(compatibleWithUserId)))`,
+ {compatibleWithUserId},
+ )
const joins = [
orderByParam === 'last_online_time' && leftJoin(userActivityJoin),
@@ -154,7 +171,8 @@ export const loadProfiles = async (props: profileQueryType) => {
joinWork && leftJoin(workJoin),
]
- const _orderBy = orderByParam === 'compatibility_score' ? 'cs.score' : `${tablePrefix}.${orderByParam}`
+ const _orderBy =
+ orderByParam === 'compatibility_score' ? 'cs.score' : `${tablePrefix}.${orderByParam}`
const afterFilter = renderSql(
select(_orderBy),
from('profiles'),
@@ -189,125 +207,126 @@ export const loadProfiles = async (props: profileQueryType) => {
)`
}
-
const filters = [
where('looking_for_matches = true'),
where(`profiles.disabled != true`),
// where(`pinned_url is not null and pinned_url != ''`),
- where(
- `(data->>'isBannedFromPosting' != 'true' or data->>'isBannedFromPosting' is null)`
- ),
+ where(`(data->>'isBannedFromPosting' != 'true' or data->>'isBannedFromPosting' is null)`),
where(`data->>'userDeleted' != 'true' or data->>'userDeleted' is null`),
- ...keywords.map(word => where(
- `lower(users.name) ilike '%' || lower($(word)) || '%'
+ ...keywords.map((word) =>
+ where(
+ `lower(users.name) ilike '%' || lower($(word)) || '%'
or lower(search_text) ilike '%' || lower($(word)) || '%'
or search_tsv @@ phraseto_tsquery('english', $(word))
OR ${getOptionClauseKeyword('interests')}
OR ${getOptionClauseKeyword('causes')}
OR ${getOptionClauseKeyword('work')}
`,
- {word, locale}
- )),
+ {word, locale},
+ ),
+ ),
genders?.length && where(`gender = ANY($(genders))`, {genders}),
- education_levels?.length && where(`education_level = ANY($(education_levels))`, {education_levels}),
+ education_levels?.length &&
+ where(`education_level = ANY($(education_levels))`, {education_levels}),
mbti?.length && where(`mbti = ANY($(mbti))`, {mbti}),
pref_gender?.length &&
- where(`pref_gender is NULL or pref_gender = '{}' OR pref_gender && $(pref_gender)`, {pref_gender}),
+ where(`pref_gender is NULL or pref_gender = '{}' OR pref_gender && $(pref_gender)`, {
+ pref_gender,
+ }),
- pref_age_min &&
- where(`age >= $(pref_age_min) or age is null`, {pref_age_min}),
+ pref_age_min && where(`age >= $(pref_age_min) or age is null`, {pref_age_min}),
- pref_age_max &&
- where(`age <= $(pref_age_max) or age is null`, {pref_age_max}),
+ pref_age_max && where(`age <= $(pref_age_max) or age is null`, {pref_age_max}),
drinks_min &&
- where(`drinks_per_month >= $(drinks_min) or drinks_per_month is null`, {drinks_min}),
+ where(`drinks_per_month >= $(drinks_min) or drinks_per_month is null`, {drinks_min}),
drinks_max &&
- where(`drinks_per_month <= $(drinks_max) or drinks_per_month is null`, {drinks_max}),
+ where(`drinks_per_month <= $(drinks_max) or drinks_per_month is null`, {drinks_max}),
big5_openness_min &&
- where(`big5_openness >= $(big5_openness_min) or big5_openness is null`, {big5_openness_min}),
+ where(`big5_openness >= $(big5_openness_min) or big5_openness is null`, {big5_openness_min}),
big5_openness_max &&
- where(`big5_openness <= $(big5_openness_max) or big5_openness is null`, {big5_openness_max}),
+ where(`big5_openness <= $(big5_openness_max) or big5_openness is null`, {big5_openness_max}),
big5_conscientiousness_min &&
- where(
- `big5_conscientiousness >= $(big5_conscientiousness_min) or big5_conscientiousness is null`,
- {big5_conscientiousness_min}
- ),
+ where(
+ `big5_conscientiousness >= $(big5_conscientiousness_min) or big5_conscientiousness is null`,
+ {big5_conscientiousness_min},
+ ),
big5_conscientiousness_max &&
- where(
- `big5_conscientiousness <= $(big5_conscientiousness_max) or big5_conscientiousness is null`,
- {big5_conscientiousness_max}
- ),
+ where(
+ `big5_conscientiousness <= $(big5_conscientiousness_max) or big5_conscientiousness is null`,
+ {big5_conscientiousness_max},
+ ),
big5_extraversion_min &&
- where(`big5_extraversion >= $(big5_extraversion_min) or big5_extraversion is null`, {big5_extraversion_min}),
+ where(`big5_extraversion >= $(big5_extraversion_min) or big5_extraversion is null`, {
+ big5_extraversion_min,
+ }),
big5_extraversion_max &&
- where(`big5_extraversion <= $(big5_extraversion_max) or big5_extraversion is null`, {big5_extraversion_max}),
+ where(`big5_extraversion <= $(big5_extraversion_max) or big5_extraversion is null`, {
+ big5_extraversion_max,
+ }),
big5_agreeableness_min &&
- where(`big5_agreeableness >= $(big5_agreeableness_min) or big5_agreeableness is null`, {big5_agreeableness_min}),
+ where(`big5_agreeableness >= $(big5_agreeableness_min) or big5_agreeableness is null`, {
+ big5_agreeableness_min,
+ }),
big5_agreeableness_max &&
- where(`big5_agreeableness <= $(big5_agreeableness_max) or big5_agreeableness is null`, {big5_agreeableness_max}),
+ where(`big5_agreeableness <= $(big5_agreeableness_max) or big5_agreeableness is null`, {
+ big5_agreeableness_max,
+ }),
big5_neuroticism_min &&
- where(`big5_neuroticism >= $(big5_neuroticism_min) or big5_neuroticism is null`, {big5_neuroticism_min}),
+ where(`big5_neuroticism >= $(big5_neuroticism_min) or big5_neuroticism is null`, {
+ big5_neuroticism_min,
+ }),
big5_neuroticism_max &&
- where(`big5_neuroticism <= $(big5_neuroticism_max) or big5_neuroticism is null`, {big5_neuroticism_max}),
+ where(`big5_neuroticism <= $(big5_neuroticism_max) or big5_neuroticism is null`, {
+ big5_neuroticism_max,
+ }),
pref_relation_styles?.length &&
- where(
- `pref_relation_styles IS NULL OR pref_relation_styles = '{}' OR pref_relation_styles && $(pref_relation_styles)`,
- {pref_relation_styles}
- ),
+ where(
+ `pref_relation_styles IS NULL OR pref_relation_styles = '{}' OR pref_relation_styles && $(pref_relation_styles)`,
+ {pref_relation_styles},
+ ),
pref_romantic_styles?.length &&
- where(
- `pref_romantic_styles IS NULL OR pref_romantic_styles = '{}' OR pref_romantic_styles && $(pref_romantic_styles)`,
- {pref_romantic_styles}
- ),
+ where(
+ `pref_romantic_styles IS NULL OR pref_romantic_styles = '{}' OR pref_romantic_styles && $(pref_romantic_styles)`,
+ {pref_romantic_styles},
+ ),
- diet?.length &&
- where(
- `diet IS NULL OR diet = '{}' OR diet && $(diet)`,
- {diet}
- ),
+ diet?.length && where(`diet IS NULL OR diet = '{}' OR diet && $(diet)`, {diet}),
political_beliefs?.length &&
- where(
- `political_beliefs IS NULL OR political_beliefs = '{}' OR political_beliefs && $(political_beliefs)`,
- {political_beliefs}
- ),
+ where(
+ `political_beliefs IS NULL OR political_beliefs = '{}' OR political_beliefs && $(political_beliefs)`,
+ {political_beliefs},
+ ),
relationship_status?.length &&
- where(
- `relationship_status IS NULL OR relationship_status = '{}' OR relationship_status && $(relationship_status)`,
- {relationship_status}
- ),
+ where(
+ `relationship_status IS NULL OR relationship_status = '{}' OR relationship_status && $(relationship_status)`,
+ {relationship_status},
+ ),
- languages?.length &&
- where(
- `languages && $(languages)`,
- {languages}
- ),
+ languages?.length && where(`languages && $(languages)`, {languages}),
religion?.length &&
- where(
- `religion IS NULL OR religion = '{}' OR religion && $(religion)`,
- {religion}
- ),
+ where(`religion IS NULL OR religion = '{}' OR religion && $(religion)`, {religion}),
interests?.length && where(getManyToManyClause('interests'), {values: interests.map(Number)}),
@@ -316,27 +335,31 @@ export const loadProfiles = async (props: profileQueryType) => {
work?.length && where(getManyToManyClause('work'), {values: work.map(Number)}),
!!wants_kids_strength &&
- wants_kids_strength !== -1 &&
- where(
- 'wants_kids_strength = -1 OR wants_kids_strength IS NULL OR ' + (wants_kids_strength >= 2 ? `wants_kids_strength >= $(wants_kids_strength)` : `wants_kids_strength <= $(wants_kids_strength)`),
- {wants_kids_strength}
- ),
+ wants_kids_strength !== -1 &&
+ where(
+ 'wants_kids_strength = -1 OR wants_kids_strength IS NULL OR ' +
+ (wants_kids_strength >= 2
+ ? `wants_kids_strength >= $(wants_kids_strength)`
+ : `wants_kids_strength <= $(wants_kids_strength)`),
+ {wants_kids_strength},
+ ),
has_kids === 0 && where(`has_kids IS NULL OR has_kids = 0`),
has_kids && has_kids > 0 && where(`has_kids > 0`),
- is_smoker !== undefined && (
+ is_smoker !== undefined &&
where(
(is_smoker ? '' : 'is_smoker IS NULL OR ') + // smokers are rare, so we don't include the people who didn't answer if we're looking for smokers
- `is_smoker = $(is_smoker)`, {is_smoker}
- )
- ),
+ `is_smoker = $(is_smoker)`,
+ {is_smoker},
+ ),
- geodbCityIds?.length &&
- where(`geodb_city_id = ANY($(geodbCityIds))`, {geodbCityIds}),
+ geodbCityIds?.length && where(`geodb_city_id = ANY($(geodbCityIds))`, {geodbCityIds}),
// miles par degree of lat: earth's radius (3950 miles) * pi / 180 = 69.0
- filterLocation && where(`
+ filterLocation &&
+ where(
+ `
city_latitude BETWEEN $(target_lat) - ($(radius) / 69.0)
AND $(target_lat) + ($(radius) / 69.0)
AND city_longitude BETWEEN $(target_lon) - ($(radius) / (69.0 * COS(RADIANS($(target_lat)))))
@@ -345,27 +368,36 @@ export const loadProfiles = async (props: profileQueryType) => {
POWER(city_latitude - $(target_lat), 2)
+ POWER((city_longitude - $(target_lon)) * COS(RADIANS($(target_lat))), 2)
) <= $(radius) / 69.0
- `, {target_lat: lat, target_lon: lon, radius}),
+ `,
+ {target_lat: lat, target_lon: lon, radius},
+ ),
skipId && where(`profiles.user_id != $(skipId)`, {skipId}),
- !shortBio && where(
- `bio_length >= ${100}
+ !shortBio &&
+ where(
+ `bio_length >= ${100}
OR array_length(profile_work.work, 1) > 0
OR array_length(profile_interests.interests, 1) > 0
OR occupation_title IS NOT NULL
- `),
+ `,
+ ),
- lastModificationWithin && where(`last_modification_time >= NOW() - INTERVAL $(lastModificationWithin)`, {lastModificationWithin}),
+ lastModificationWithin &&
+ where(`last_modification_time >= NOW() - INTERVAL $(lastModificationWithin)`, {
+ lastModificationWithin,
+ }),
// Exclude profiles that the requester has chosen to hide
- userId && where(
- `NOT EXISTS (
+ userId &&
+ where(
+ `NOT EXISTS (
SELECT 1 FROM hidden_profiles hp
WHERE hp.hider_user_id = $(userId)
AND hp.hidden_user_id = profiles.user_id
- )`, {userId}
- ),
+ )`,
+ {userId},
+ ),
]
let selectCols = 'profiles.*, users.name, users.username, users.data as user'
@@ -393,11 +425,7 @@ export const loadProfiles = async (props: profileQueryType) => {
// console.debug('profiles:', profiles)
- const countQuery = renderSql(
- select(`count(*) as count`),
- ...tableSelection,
- ...filters,
- )
+ const countQuery = renderSql(select(`count(*) as count`), ...tableSelection, ...filters)
const count = await pg.one(countQuery, [], (r) => Number(r.count))
diff --git a/backend/api/src/get-supabase-token.ts b/backend/api/src/get-supabase-token.ts
index 59df7fe7..2605277e 100644
--- a/backend/api/src/get-supabase-token.ts
+++ b/backend/api/src/get-supabase-token.ts
@@ -1,11 +1,9 @@
+import {ENV_CONFIG} from 'common/envs/constants'
import {sign} from 'jsonwebtoken'
-import {APIError, APIHandler} from './helpers/endpoint'
-import {ENV_CONFIG} from "common/envs/constants";
-export const getSupabaseToken: APIHandler<'get-supabase-token'> = async (
- _,
- auth
-) => {
+import {APIError, APIHandler} from './helpers/endpoint'
+
+export const getSupabaseToken: APIHandler<'get-supabase-token'> = async (_, auth) => {
const jwtSecret = process.env.SUPABASE_JWT_SECRET
if (jwtSecret == null) {
throw new APIError(500, "No SUPABASE_JWT_SECRET; couldn't sign token.")
diff --git a/backend/api/src/get-user-data-export.ts b/backend/api/src/get-user-data-export.ts
index 8df3df34..82dfba0c 100644
--- a/backend/api/src/get-user-data-export.ts
+++ b/backend/api/src/get-user-data-export.ts
@@ -1,22 +1,20 @@
-import {APIHandler} from './helpers/endpoint'
-import {createSupabaseDirectClient} from 'shared/supabase/init'
import {Row} from 'common/supabase/utils'
-import {getLikesAndShipsMain} from './get-likes-and-ships'
import {parseJsonContentToText} from 'common/util/parse'
-import {parseMessageObject} from "shared/supabase/messages";
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {parseMessageObject} from 'shared/supabase/messages'
+
+import {getLikesAndShipsMain} from './get-likes-and-ships'
+import {APIHandler} from './helpers/endpoint'
export const getUserDataExport: APIHandler<'me/data'> = async (_, auth) => {
const userId = auth.uid
const pg = createSupabaseDirectClient()
- const user = await pg.oneOrNone>(
- 'select * from users where id = $1',
- [userId]
- )
+ const user = await pg.oneOrNone>('select * from users where id = $1', [userId])
const privateUser = await pg.oneOrNone>(
'select * from private_users where id = $1',
- [userId]
+ [userId],
)
const profile = await pg.oneOrNone(
@@ -47,7 +45,7 @@ export const getUserDataExport: APIHandler<'me/data'> = async (_, auth) => {
group by pw.profile_id) as profile_work on profile_work.profile_id = profiles.id
where profiles.user_id = $1
`,
- [userId]
+ [userId],
)
if (profile.bio) {
@@ -68,17 +66,17 @@ export const getUserDataExport: APIHandler<'me/data'> = async (_, auth) => {
where a.creator_id = $1
order by a.created_time desc
`,
- [userId]
+ [userId],
)
const userActivity = await pg.oneOrNone>(
'select * from user_activity where user_id = $1',
- [userId]
+ [userId],
)
const searchBookmarks = await pg.manyOrNone>(
'select * from bookmarked_searches where creator_id = $1 order by id desc',
- [userId]
+ [userId],
)
const hiddenProfiles = await pg.manyOrNone(
@@ -87,41 +85,36 @@ export const getUserDataExport: APIHandler<'me/data'> = async (_, auth) => {
join users u on u.id = hp.hidden_user_id
where hp.hider_user_id = $1
order by hp.id desc`,
- [userId]
+ [userId],
)
const messageChannelMemberships = await pg.manyOrNone<
Row<'private_user_message_channel_members'>
- >(
- 'select * from private_user_message_channel_members where user_id = $1',
- [userId]
- )
+ >('select * from private_user_message_channel_members where user_id = $1', [userId])
- const channelIds = Array.from(
- new Set(messageChannelMemberships.map((m) => m.channel_id))
- )
+ const channelIds = Array.from(new Set(messageChannelMemberships.map((m) => m.channel_id)))
const messageChannels = channelIds.length
? await pg.manyOrNone>(
- 'select * from private_user_message_channels where id = any($1)',
- [channelIds]
- )
+ 'select * from private_user_message_channels where id = any($1)',
+ [channelIds],
+ )
: []
const messages = channelIds.length
? await pg.manyOrNone>(
- `select *
+ `select *
from private_user_messages
where channel_id = any ($1)
order by created_time`,
- [channelIds]
- )
+ [channelIds],
+ )
: []
for (const message of messages) parseMessageObject(message)
const membershipsWithUsernames = channelIds.length
? await pg.manyOrNone(
- `
+ `
select m.*,
u.username
from private_user_message_channel_members m
@@ -129,8 +122,8 @@ export const getUserDataExport: APIHandler<'me/data'> = async (_, auth) => {
where m.channel_id = any ($1)
and m.user_id != $2
`,
- [channelIds, userId]
- )
+ [channelIds, userId],
+ )
: []
const endorsements = await getLikesAndShipsMain(userId)
@@ -153,17 +146,17 @@ export const getUserDataExport: APIHandler<'me/data'> = async (_, auth) => {
where r.user_id = $1
order by v.created_time desc
`,
- [userId]
+ [userId],
)
const reports = await pg.manyOrNone>(
'select * from reports where user_id = $1 order by created_time desc nulls last',
- [userId]
+ [userId],
)
const contactMessages = await pg.manyOrNone>(
'select * from contact where user_id = $1 order by created_time desc nulls last',
- [userId]
+ [userId],
)
return {
@@ -185,4 +178,3 @@ export const getUserDataExport: APIHandler<'me/data'> = async (_, auth) => {
accountMetadata,
}
}
-
diff --git a/backend/api/src/get-user.ts b/backend/api/src/get-user.ts
index a550268c..48a4fb3e 100644
--- a/backend/api/src/get-user.ts
+++ b/backend/api/src/get-user.ts
@@ -1,15 +1,15 @@
-import { toUserAPIResponse } from 'common/api/user-types'
-import { convertUser } from 'common/supabase/users'
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { APIError } from 'common/api/utils'
+import {toUserAPIResponse} from 'common/api/user-types'
+import {APIError} from 'common/api/utils'
+import {convertUser} from 'common/supabase/users'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
-export const getUser = async (props: { id: string } | { username: string }) => {
+export const getUser = async (props: {id: string} | {username: string}) => {
const pg = createSupabaseDirectClient()
const user = await pg.oneOrNone(
`select * from users
where ${'id' in props ? 'id' : 'username'} = $1`,
['id' in props ? props.id : props.username],
- (r) => (r ? convertUser(r) : null)
+ (r) => (r ? convertUser(r) : null),
)
if (!user) throw new APIError(404, 'User not found')
diff --git a/backend/api/src/has-free-like.ts b/backend/api/src/has-free-like.ts
index df7158c7..fd7bebb1 100644
--- a/backend/api/src/has-free-like.ts
+++ b/backend/api/src/has-free-like.ts
@@ -1,10 +1,7 @@
-import { type APIHandler } from 'api/helpers/endpoint'
-import { createSupabaseDirectClient } from 'shared/supabase/init'
+import {type APIHandler} from 'api/helpers/endpoint'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
-export const hasFreeLike: APIHandler<'has-free-like'> = async (
- _props,
- auth
-) => {
+export const hasFreeLike: APIHandler<'has-free-like'> = async (_props, auth) => {
return {
status: 'success',
hasFreeLike: await getHasFreeLike(auth.uid),
@@ -23,7 +20,7 @@ export const getHasFreeLike = async (userId: string) => {
and created_time at time zone 'UTC' at time zone 'America/Los_Angeles' < ((now() at time zone 'UTC' at time zone 'America/Los_Angeles')::date + interval '1 day')
limit 1
`,
- [userId]
+ [userId],
)
return !likeGivenToday
}
diff --git a/backend/api/src/health.ts b/backend/api/src/health.ts
index 6a69db6a..46c6db63 100644
--- a/backend/api/src/health.ts
+++ b/backend/api/src/health.ts
@@ -1,6 +1,6 @@
-import { APIHandler } from './helpers/endpoint'
import {git} from './../metadata.json'
import {version as pkgVersion} from './../package.json'
+import {APIHandler} from './helpers/endpoint'
export const health: APIHandler<'health'> = async (_, auth) => {
return {
diff --git a/backend/api/src/helpers/endpoint.ts b/backend/api/src/helpers/endpoint.ts
index 88ed8f61..78047cad 100644
--- a/backend/api/src/helpers/endpoint.ts
+++ b/backend/api/src/helpers/endpoint.ts
@@ -1,11 +1,16 @@
-import * as admin from 'firebase-admin'
-import {z} from 'zod'
-import {NextFunction, Request, Response} from 'express'
-
-import {PrivateUser} from 'common/user'
+import {
+ API,
+ APIPath,
+ APIResponseOptionalContinue,
+ APISchema,
+ ValidatedAPIParams,
+} from 'common/api/schema'
import {APIError} from 'common/api/utils'
-import {API, APIPath, APIResponseOptionalContinue, APISchema, ValidatedAPIParams,} from 'common/api/schema'
+import {PrivateUser} from 'common/user'
+import {NextFunction, Request, Response} from 'express'
+import * as admin from 'firebase-admin'
import {getPrivateUserByKey, log} from 'shared/utils'
+import {z} from 'zod'
export {APIError} from 'common/api/utils'
@@ -27,10 +32,10 @@ export {APIError} from 'common/api/utils'
export type AuthedUser = {
uid: string
- creds: JwtCredentials | (KeyCredentials & { privateUser: PrivateUser })
+ creds: JwtCredentials | (KeyCredentials & {privateUser: PrivateUser})
}
-type JwtCredentials = { kind: 'jwt'; data: admin.auth.DecodedIdToken }
-type KeyCredentials = { kind: 'key'; data: string }
+type JwtCredentials = {kind: 'jwt'; data: admin.auth.DecodedIdToken}
+type KeyCredentials = {kind: 'key'; data: string}
type Credentials = JwtCredentials | KeyCredentials
// export async function verifyIdToken(payload: string): Promise {
@@ -76,8 +81,8 @@ export const parseCredentials = async (req: Request): Promise => {
try {
return {kind: 'jwt', data: await auth.verifyIdToken(payload)}
} catch (err) {
- const raw = payload.split(".")[0];
- console.log("JWT header:", JSON.parse(Buffer.from(raw, "base64").toString()));
+ const raw = payload.split('.')[0]
+ console.log('JWT header:', JSON.parse(Buffer.from(raw, 'base64').toString()))
// This is somewhat suspicious, so get it into the firebase console
console.error('Error verifying Firebase JWT: ', err, scheme, payload)
throw new APIError(500, 'Error validating token.')
@@ -170,10 +175,8 @@ export const validate = (schema: T, val: unknown) => {
export type APIHandler = (
props: ValidatedAPIParams,
- auth: APISchema extends { authed: true }
- ? AuthedUser
- : AuthedUser | undefined,
- req: Request
+ auth: APISchema extends {authed: true} ? AuthedUser : AuthedUser | undefined,
+ req: Request,
) => Promise>
// Simple in-memory fixed-window rate limiter keyed by auth uid (or IP if unauthenticated)
@@ -182,7 +185,7 @@ export type APIHandler = (
// API_RATE_LIMIT_PER_MIN_AUTHED
// API_RATE_LIMIT_PER_MIN_UNAUTHED
// Endpoints can be exempted by adding their name to RATE_LIMIT_EXEMPT (comma-separated)
-const __rateLimitState: Map = new Map()
+const __rateLimitState: Map = new Map()
function getRateLimitConfig() {
const authed = Number(process.env.API_RATE_LIMIT_PER_MIN_AUTHED ?? 120)
@@ -228,11 +231,13 @@ function checkRateLimit(name: string, req: Request, res: Response, auth?: Authed
}
}
-export const typedEndpoint = (
- name: N,
- handler: APIHandler
-) => {
- const {props: propSchema, authed: authRequired, rateLimited = false, method} = API[name] as APISchema
+export const typedEndpoint = (name: N, handler: APIHandler) => {
+ const {
+ props: propSchema,
+ authed: authRequired,
+ rateLimited = false,
+ method,
+ } = API[name] as APISchema
return async (req: Request, res: Response, next: NextFunction) => {
let authUser: AuthedUser | undefined = undefined
@@ -260,16 +265,14 @@ export const typedEndpoint = (
const resultOptionalContinue = await handler(
validate(propSchema, props),
authUser as AuthedUser,
- req
+ req,
)
const hasContinue =
resultOptionalContinue &&
'continue' in resultOptionalContinue &&
'result' in resultOptionalContinue
- const result = hasContinue
- ? resultOptionalContinue.result
- : resultOptionalContinue
+ const result = hasContinue ? resultOptionalContinue.result : resultOptionalContinue
if (!res.headersSent) {
// Convert bigint to number, b/c JSON doesn't support bigint.
diff --git a/backend/api/src/helpers/private-messages.ts b/backend/api/src/helpers/private-messages.ts
index ddf9c1dd..7b0b7e67 100644
--- a/backend/api/src/helpers/private-messages.ts
+++ b/backend/api/src/helpers/private-messages.ts
@@ -1,23 +1,23 @@
-import {Json} from 'common/supabase/schema'
-import {SupabaseDirectClient} from 'shared/supabase/init'
-import {ChatVisibility} from 'common/chat-message'
-import {User} from 'common/user'
-import {first} from 'lodash'
-import {log} from 'shared/monitoring/log'
-import {getPrivateUser, getUser} from 'shared/utils'
import {type JSONContent} from '@tiptap/core'
import {APIError} from 'common/api/utils'
-import {broadcast} from 'shared/websockets/server'
-import {track} from 'shared/analytics'
-import {sendNewMessageEmail} from 'email/functions/helpers'
+import {ChatVisibility} from 'common/chat-message'
+import {Json} from 'common/supabase/schema'
+import {User} from 'common/user'
+import {parseJsonContentToText} from 'common/util/parse'
import dayjs from 'dayjs'
-import utc from 'dayjs/plugin/utc'
import timezone from 'dayjs/plugin/timezone'
-import webPush from 'web-push'
-import {parseJsonContentToText} from "common/util/parse"
-import {encryptMessage} from "shared/encryption"
+import utc from 'dayjs/plugin/utc'
+import {sendNewMessageEmail} from 'email/functions/helpers'
import * as admin from 'firebase-admin'
-import {TokenMessage} from "firebase-admin/lib/messaging/messaging-api";
+import {TokenMessage} from 'firebase-admin/lib/messaging/messaging-api'
+import {first} from 'lodash'
+import {track} from 'shared/analytics'
+import {encryptMessage} from 'shared/encryption'
+import {log} from 'shared/monitoring/log'
+import {SupabaseDirectClient} from 'shared/supabase/init'
+import {getPrivateUser, getUser} from 'shared/utils'
+import {broadcast} from 'shared/websockets/server'
+import webPush from 'web-push'
dayjs.extend(utc)
dayjs.extend(timezone)
@@ -48,7 +48,7 @@ export const insertPrivateMessage = async (
channelId: number,
userId: string,
visibility: ChatVisibility,
- pg: SupabaseDirectClient
+ pg: SupabaseDirectClient,
) => {
const plaintext = JSON.stringify(content)
const {ciphertext, iv, tag} = encryptMessage(plaintext)
@@ -56,20 +56,20 @@ export const insertPrivateMessage = async (
`insert into private_user_messages (ciphertext, iv, tag, channel_id, user_id, visibility)
values ($1, $2, $3, $4, $5, $6)
returning created_time`,
- [ciphertext, iv, tag, channelId, userId, visibility]
+ [ciphertext, iv, tag, channelId, userId, visibility],
)
await pg.none(
`update private_user_message_channels
set last_updated_time = $1
where id = $2`,
- [lastMessage.created_time, channelId]
+ [lastMessage.created_time, channelId],
)
}
export const addUsersToPrivateMessageChannel = async (
userIds: string[],
channelId: number,
- pg: SupabaseDirectClient
+ pg: SupabaseDirectClient,
) => {
await Promise.all(
userIds.map((id) =>
@@ -78,15 +78,15 @@ export const addUsersToPrivateMessageChannel = async (
values ($1, $2, 'member', 'proposed')
on conflict do nothing
`,
- [channelId, id]
- )
- )
+ [channelId, id],
+ ),
+ ),
)
await pg.none(
`update private_user_message_channels
set last_updated_time = now()
where id = $1`,
- [channelId]
+ [channelId],
)
}
@@ -103,12 +103,12 @@ export async function broadcastPrivateMessages(
and status != 'left'
`,
[channelId, userId],
- (r) => r.user_id
+ (r) => r.user_id,
)
otherUserIds.concat(userId).forEach((otherUserId) => {
broadcast(`private-user-messages/${otherUserId}`, {})
})
- return otherUserIds;
+ return otherUserIds
}
export const createPrivateUserMessageMain = async (
@@ -116,7 +116,7 @@ export const createPrivateUserMessageMain = async (
channelId: number,
content: JSONContent,
pg: SupabaseDirectClient,
- visibility: ChatVisibility
+ visibility: ChatVisibility,
) => {
log('createPrivateUserMessageMain', creator, channelId, content)
@@ -126,10 +126,9 @@ export const createPrivateUserMessageMain = async (
from private_user_message_channel_members
where channel_id = $1
and user_id = $2`,
- [channelId, creator.id]
+ [channelId, creator.id],
)
- if (!authorized)
- throw new APIError(403, 'You are not authorized to post to this channel')
+ if (!authorized) throw new APIError(403, 'You are not authorized to post to this channel')
await insertPrivateMessage(content, channelId, creator.id, visibility, pg)
@@ -138,13 +137,12 @@ export const createPrivateUserMessageMain = async (
channel_id: channelId,
user_id: creator.id,
}
- const otherUserIds = await broadcastPrivateMessages(pg, channelId, creator.id);
+ const otherUserIds = await broadcastPrivateMessages(pg, channelId, creator.id)
// Fire and forget safely
- void notifyOtherUserInChannelIfInactive(channelId, creator, content, pg)
- .catch((err) => {
- console.error('notifyOtherUserInChannelIfInactive failed', err)
- })
+ void notifyOtherUserInChannelIfInactive(channelId, creator, content, pg).catch((err) => {
+ console.error('notifyOtherUserInChannelIfInactive failed', err)
+ })
track(creator.id, 'send private message', {
channelId,
@@ -158,16 +156,16 @@ const notifyOtherUserInChannelIfInactive = async (
channelId: number,
creator: User,
content: JSONContent,
- pg: SupabaseDirectClient
+ pg: SupabaseDirectClient,
) => {
- const otherUserIds = await pg.manyOrNone<{ user_id: string }>(
+ const otherUserIds = await pg.manyOrNone<{user_id: string}>(
`select user_id
from private_user_message_channel_members
where channel_id = $1
and user_id != $2
and status != 'left'
`,
- [channelId, creator.id]
+ [channelId, creator.id],
)
// We're only sending notifs for 1:1 channels
if (!otherUserIds || otherUserIds.length > 1) return
@@ -191,10 +189,7 @@ const notifyOtherUserInChannelIfInactive = async (
await sendWebNotifications(pg, receiverId, JSON.stringify(payload))
await sendMobileNotifications(pg, receiverId, payload)
- const startOfDay = dayjs()
- .tz('America/Los_Angeles')
- .startOf('day')
- .toISOString()
+ const startOfDay = dayjs().tz('America/Los_Angeles').startOf('day').toISOString()
const previousMessagesThisDayBetweenTheseUsers = await pg.one(
`select count(*)
from private_user_messages
@@ -202,7 +197,7 @@ const notifyOtherUserInChannelIfInactive = async (
and user_id = $2
and created_time > $3
`,
- [channelId, creator.id, startOfDay]
+ [channelId, creator.id, startOfDay],
)
log('previous messages this day', previousMessagesThisDayBetweenTheseUsers)
if (previousMessagesThisDayBetweenTheseUsers.count > 1) return
@@ -210,27 +205,18 @@ const notifyOtherUserInChannelIfInactive = async (
await createNewMessageNotification(creator, receiver, channelId)
}
-const createNewMessageNotification = async (
- fromUser: User,
- toUser: User,
- channelId: number,
-) => {
+const createNewMessageNotification = async (fromUser: User, toUser: User, channelId: number) => {
const privateUser = await getPrivateUser(toUser.id)
console.debug('privateUser:', privateUser)
if (!privateUser) return
await sendNewMessageEmail(privateUser, fromUser, toUser, channelId)
}
-
-async function sendWebNotifications(
- pg: SupabaseDirectClient,
- userId: string,
- payload: string,
-) {
+async function sendWebNotifications(pg: SupabaseDirectClient, userId: string, payload: string) {
webPush.setVapidDetails(
'mailto:hello@compassmeet.com',
process.env.VAPID_PUBLIC_KEY!,
- process.env.VAPID_PRIVATE_KEY!
+ process.env.VAPID_PRIVATE_KEY!,
)
// Retrieve subscription from the database
const subscriptions = await getSubscriptionsFromDB(pg, userId)
@@ -250,20 +236,18 @@ async function sendWebNotifications(
}
}
-
-export async function getSubscriptionsFromDB(
- pg: SupabaseDirectClient,
- userId: string,
-) {
+export async function getSubscriptionsFromDB(pg: SupabaseDirectClient, userId: string) {
try {
- const subscriptions = await pg.manyOrNone(`
+ const subscriptions = await pg.manyOrNone(
+ `
select endpoint, keys
from push_subscriptions
where user_id = $1
- `, [userId]
+ `,
+ [userId],
)
- return subscriptions.map(sub => ({
+ return subscriptions.map((sub) => ({
endpoint: sub.endpoint,
keys: sub.keys,
}))
@@ -273,35 +257,26 @@ export async function getSubscriptionsFromDB(
}
}
-async function removeSubscription(
- pg: SupabaseDirectClient,
- endpoint: any,
- userId: string,
-) {
+async function removeSubscription(pg: SupabaseDirectClient, endpoint: any, userId: string) {
await pg.none(
`DELETE
FROM push_subscriptions
WHERE endpoint = $1
AND user_id = $2`,
- [endpoint, userId]
+ [endpoint, userId],
)
}
-async function removeMobileSubscription(
- pg: SupabaseDirectClient,
- token: any,
- userId: string,
-) {
+async function removeMobileSubscription(pg: SupabaseDirectClient, token: any, userId: string) {
await pg.none(
`DELETE
FROM push_subscriptions_mobile
WHERE token = $1
AND user_id = $2`,
- [token, userId]
+ [token, userId],
)
}
-
async function sendMobileNotifications(
pg: SupabaseDirectClient,
userId: string,
@@ -349,13 +324,15 @@ export async function sendPushToToken(
} catch (err: unknown) {
// Check if it's a Firebase Messaging error
if (err instanceof Error && 'code' in err) {
- const firebaseError = err as { code: string; message: string }
+ const firebaseError = err as {code: string; message: string}
console.warn('Firebase error:', firebaseError.code, firebaseError.message)
// Handle specific error cases here if needed
// For example, if token is no longer valid:
- if (firebaseError.code === 'messaging/registration-token-not-registered' ||
- firebaseError.code === 'messaging/invalid-argument') {
+ if (
+ firebaseError.code === 'messaging/registration-token-not-registered' ||
+ firebaseError.code === 'messaging/invalid-argument'
+ ) {
console.warn('Removing invalid FCM token')
await removeMobileSubscription(pg, token, userId)
}
@@ -366,17 +343,15 @@ export async function sendPushToToken(
return
}
-
-export async function getMobileSubscriptionsFromDB(
- pg: SupabaseDirectClient,
- userId: string,
-) {
+export async function getMobileSubscriptionsFromDB(pg: SupabaseDirectClient, userId: string) {
try {
- const subscriptions = await pg.manyOrNone(`
+ const subscriptions = await pg.manyOrNone(
+ `
select token
from push_subscriptions_mobile
where user_id = $1
- `, [userId]
+ `,
+ [userId],
)
return subscriptions
diff --git a/backend/api/src/hide-comment.ts b/backend/api/src/hide-comment.ts
index 3a57e542..8f19fe21 100644
--- a/backend/api/src/hide-comment.ts
+++ b/backend/api/src/hide-comment.ts
@@ -1,35 +1,25 @@
-import { APIError, APIHandler } from 'api/helpers/endpoint'
-import { isAdminId } from 'common/envs/constants'
-import { convertComment } from 'common/supabase/comment'
-import { Row } from 'common/supabase/utils'
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { broadcastUpdatedComment } from 'shared/websockets/helpers'
+import {APIError, APIHandler} from 'api/helpers/endpoint'
+import {isAdminId} from 'common/envs/constants'
+import {convertComment} from 'common/supabase/comment'
+import {Row} from 'common/supabase/utils'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {broadcastUpdatedComment} from 'shared/websockets/helpers'
-export const hideComment: APIHandler<'hide-comment'> = async (
- { commentId, hide },
- auth
-) => {
+export const hideComment: APIHandler<'hide-comment'> = async ({commentId, hide}, auth) => {
const pg = createSupabaseDirectClient()
const comment = await pg.oneOrNone>(
`select * from profile_comments where id = $1`,
- [commentId]
+ [commentId],
)
if (!comment) {
throw new APIError(404, 'Comment not found')
}
- if (
- !isAdminId(auth.uid) &&
- comment.user_id !== auth.uid &&
- comment.on_user_id !== auth.uid
- ) {
+ if (!isAdminId(auth.uid) && comment.user_id !== auth.uid && comment.on_user_id !== auth.uid) {
throw new APIError(403, 'You are not allowed to hide this comment')
}
- await pg.none(`update profile_comments set hidden = $2 where id = $1`, [
- commentId,
- hide,
- ])
+ await pg.none(`update profile_comments set hidden = $2 where id = $1`, [commentId, hide])
broadcastUpdatedComment(convertComment(comment))
}
diff --git a/backend/api/src/hide-profile.ts b/backend/api/src/hide-profile.ts
index d777f2b5..2e071d49 100644
--- a/backend/api/src/hide-profile.ts
+++ b/backend/api/src/hide-profile.ts
@@ -1,14 +1,11 @@
-import {APIError, APIHandler} from './helpers/endpoint'
import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {APIError, APIHandler} from './helpers/endpoint'
+
// Hide a profile for the requesting user by inserting a row into hidden_profiles.
// Idempotent: if the pair already exists, succeed silently.
-export const hideProfile: APIHandler<'hide-profile'> = async (
- {hiddenUserId},
- auth
-) => {
- if (auth.uid === hiddenUserId)
- throw new APIError(400, 'You cannot hide yourself')
+export const hideProfile: APIHandler<'hide-profile'> = async ({hiddenUserId}, auth) => {
+ if (auth.uid === hiddenUserId) throw new APIError(400, 'You cannot hide yourself')
const pg = createSupabaseDirectClient()
@@ -17,7 +14,7 @@ export const hideProfile: APIHandler<'hide-profile'> = async (
`insert into hidden_profiles (hider_user_id, hidden_user_id)
values ($1, $2)
on conflict (hider_user_id, hidden_user_id) do nothing`,
- [auth.uid, hiddenUserId]
+ [auth.uid, hiddenUserId],
)
return {status: 'success'}
diff --git a/backend/api/src/leave-private-user-message-channel.ts b/backend/api/src/leave-private-user-message-channel.ts
index 55e1c96e..f9862678 100644
--- a/backend/api/src/leave-private-user-message-channel.ts
+++ b/backend/api/src/leave-private-user-message-channel.ts
@@ -1,14 +1,11 @@
-import { APIError, APIHandler } from 'api/helpers/endpoint'
-import { log, getUser } from 'shared/utils'
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import {
- insertPrivateMessage,
- leaveChatContent,
-} from 'api/helpers/private-messages'
+import {APIError, APIHandler} from 'api/helpers/endpoint'
+import {insertPrivateMessage, leaveChatContent} from 'api/helpers/private-messages'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {getUser, log} from 'shared/utils'
export const leavePrivateUserMessageChannel: APIHandler<
'leave-private-user-message-channel'
-> = async ({ channelId }, auth) => {
+> = async ({channelId}, auth) => {
const pg = createSupabaseDirectClient()
const user = await getUser(auth.uid)
if (!user) throw new APIError(401, 'Your account was not found')
@@ -16,10 +13,9 @@ export const leavePrivateUserMessageChannel: APIHandler<
const membershipStatus = await pg.oneOrNone(
`select status from private_user_message_channel_members
where channel_id = $1 and user_id = $2`,
- [channelId, auth.uid]
+ [channelId, auth.uid],
)
- if (!membershipStatus)
- throw new APIError(403, 'You are not authorized to post to this channel')
+ if (!membershipStatus) throw new APIError(403, 'You are not authorized to post to this channel')
log('membershipStatus: ' + membershipStatus)
// add message that the user left the channel
@@ -29,15 +25,9 @@ export const leavePrivateUserMessageChannel: APIHandler<
set status = 'left'
where channel_id=$1 and user_id=$2;
`,
- [channelId, auth.uid]
+ [channelId, auth.uid],
)
- await insertPrivateMessage(
- leaveChatContent(user.name),
- channelId,
- auth.uid,
- 'system_status',
- pg
- )
- return { status: 'success', channelId: Number(channelId) }
+ await insertPrivateMessage(leaveChatContent(user.name), channelId, auth.uid, 'system_status', pg)
+ return {status: 'success', channelId: Number(channelId)}
}
diff --git a/backend/api/src/like-profile.ts b/backend/api/src/like-profile.ts
index d9d9b099..8849ab4a 100644
--- a/backend/api/src/like-profile.ts
+++ b/backend/api/src/like-profile.ts
@@ -1,42 +1,43 @@
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { APIError, APIHandler } from './helpers/endpoint'
-import { createProfileLikeNotification } from 'shared/create-profile-notification'
-import { getHasFreeLike } from './has-free-like'
-import { log } from 'shared/utils'
-import { tryCatch } from 'common/util/try-catch'
-import { Row } from 'common/supabase/utils'
+import {Row} from 'common/supabase/utils'
+import {tryCatch} from 'common/util/try-catch'
+import {createProfileLikeNotification} from 'shared/create-profile-notification'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {log} from 'shared/utils'
+
+import {getHasFreeLike} from './has-free-like'
+import {APIError, APIHandler} from './helpers/endpoint'
export const likeProfile: APIHandler<'like-profile'> = async (props, auth) => {
- const { targetUserId, remove } = props
+ const {targetUserId, remove} = props
const creatorId = auth.uid
const pg = createSupabaseDirectClient()
if (remove) {
- const { error } = await tryCatch(
- pg.none(
- 'delete from profile_likes where creator_id = $1 and target_id = $2',
- [creatorId, targetUserId]
- )
+ const {error} = await tryCatch(
+ pg.none('delete from profile_likes where creator_id = $1 and target_id = $2', [
+ creatorId,
+ targetUserId,
+ ]),
)
if (error) {
throw new APIError(500, 'Failed to remove like: ' + error.message)
}
- return { status: 'success' }
+ return {status: 'success'}
}
// Check if like already exists
- const { data: existing } = await tryCatch(
+ const {data: existing} = await tryCatch(
pg.oneOrNone>(
'select * from profile_likes where creator_id = $1 and target_id = $2',
- [creatorId, targetUserId]
- )
+ [creatorId, targetUserId],
+ ),
)
if (existing) {
log('Like already exists, do nothing')
- return { status: 'success' }
+ return {status: 'success'}
}
const hasFreeLike = await getHasFreeLike(creatorId)
@@ -47,11 +48,11 @@ export const likeProfile: APIHandler<'like-profile'> = async (props, auth) => {
}
// Insert the new like
- const { data, error } = await tryCatch(
+ const {data, error} = await tryCatch(
pg.one>(
'insert into profile_likes (creator_id, target_id) values ($1, $2) returning *',
- [creatorId, targetUserId]
- )
+ [creatorId, targetUserId],
+ ),
)
if (error) {
@@ -63,7 +64,7 @@ export const likeProfile: APIHandler<'like-profile'> = async (props, auth) => {
}
return {
- result: { status: 'success' },
+ result: {status: 'success'},
continue: continuation,
}
}
diff --git a/backend/api/src/mark-all-notifications-read.ts b/backend/api/src/mark-all-notifications-read.ts
index 7d0c0e81..92a076a7 100644
--- a/backend/api/src/mark-all-notifications-read.ts
+++ b/backend/api/src/mark-all-notifications-read.ts
@@ -1,16 +1,14 @@
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { APIHandler } from './helpers/endpoint'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
-export const markAllNotifsRead: APIHandler<'mark-all-notifs-read'> = async (
- _,
- auth
-) => {
+import {APIHandler} from './helpers/endpoint'
+
+export const markAllNotifsRead: APIHandler<'mark-all-notifs-read'> = async (_, auth) => {
const pg = createSupabaseDirectClient()
await pg.none(
`update user_notifications
SET data = jsonb_set(data, '{isSeen}', 'true'::jsonb)
where user_id = $1
and data->>'isSeen' = 'false'`,
- [auth.uid]
+ [auth.uid],
)
}
diff --git a/backend/api/src/public/swagger.css b/backend/api/src/public/swagger.css
index 838c02a3..55adaf43 100644
--- a/backend/api/src/public/swagger.css
+++ b/backend/api/src/public/swagger.css
@@ -1,106 +1,135 @@
@media (prefers-color-scheme: dark) {
- body {
- background-color: #1e1e1e !important;
- color: #ffffff !important;
-
- }
- .swagger-ui p,
- h1,
- h2,
- h3,
- h4,
- h5,
- h6,
- label,
- .btn,
- .parameter__name,
- .parameter__type,
- .parameter__in,
- .response-control-media-type__title,
- table thead tr td,
- table thead tr th,
- .tab li,
- .response-col_links,
- .opblock-summary-description {
- color: #ffffff !important;
- }
- .swagger-ui .topbar, .opblock-body select, textarea {
- background-color: #2b2b2b !important;
- color: #ffffff !important;
- }
- .swagger-ui .opblock {
- background-color: #2c2c2c !important;
- border-color: #fff !important;
- }
- .swagger-ui .opblock .opblock-summary-method {
- background-color: #1f1f1f !important;
- color: #fff !important;
- }
- .swagger-ui .opblock .opblock-section-header {
- background: #1f1f1f !important;
- color: #fff !important;
- }
- .swagger-ui .responses-wrapper {
- background-color: #1f1f1f !important;
- }
- .swagger-ui .response-col_status {
- color: #fff !important;
- }
- .swagger-ui .scheme-container {
- background-color: #1f1f1f !important;
- }
- .swagger-ui .modal-ux, input {
- background-color: #1f1f1f !important;
- color: #fff !important;
- }
- .swagger-ui svg path {
- fill: white !important;
- }
- .swagger-ui .close-modal svg {
- color: #1e90ff !important;
- }
- a {
- color: #1e90ff !important;
- }
+ body {
+ background-color: #1e1e1e !important;
+ color: #ffffff !important;
+ }
+
+ .swagger-ui p,
+ h1,
+ h2,
+ h3,
+ h4,
+ h5,
+ h6,
+ label,
+ .btn,
+ .parameter__name,
+ .parameter__type,
+ .parameter__in,
+ .response-control-media-type__title,
+ table thead tr td,
+ table thead tr th,
+ .tab li,
+ .response-col_links,
+ .opblock-summary-description {
+ color: #ffffff !important;
+ }
+
+ .swagger-ui .topbar,
+ .opblock-body select,
+ textarea {
+ background-color: #2b2b2b !important;
+ color: #ffffff !important;
+ }
+
+ .swagger-ui .opblock {
+ background-color: #2c2c2c !important;
+ border-color: #fff !important;
+ }
+
+ .swagger-ui .opblock .opblock-summary-method {
+ background-color: #1f1f1f !important;
+ color: #fff !important;
+ }
+
+ .swagger-ui .opblock .opblock-section-header {
+ background: #1f1f1f !important;
+ color: #fff !important;
+ }
+
+ .swagger-ui .responses-wrapper {
+ background-color: #1f1f1f !important;
+ }
+
+ .swagger-ui .response-col_status {
+ color: #fff !important;
+ }
+
+ .swagger-ui .scheme-container {
+ background-color: #1f1f1f !important;
+ }
+
+ .swagger-ui .modal-ux,
+ input {
+ background-color: #1f1f1f !important;
+ color: #fff !important;
+ }
+
+ .swagger-ui svg path {
+ fill: white !important;
+ }
+
+ .swagger-ui .close-modal svg {
+ color: #1e90ff !important;
+ }
+
+ a {
+ color: #1e90ff !important;
+ }
}
/* Increase font sizes on mobile for better readability */
/* Still not working though */
@media (max-width: 640px) {
- html,
- body,
- .swagger-ui {
- font-size: 32px !important;
- line-height: 1.5 !important;
- }
+ html,
+ body,
+ .swagger-ui {
+ font-size: 32px !important;
+ line-height: 1.5 !important;
+ }
- .swagger-ui {
- -webkit-text-size-adjust: 100%;
- text-size-adjust: 100%;
- }
+ .swagger-ui {
+ -webkit-text-size-adjust: 100%;
+ text-size-adjust: 100%;
+ }
- /* Common text elements */
- .swagger-ui p,
- .swagger-ui label,
- .swagger-ui .btn,
- .swagger-ui .parameter__name,
- .swagger-ui .parameter__type,
- .swagger-ui .parameter__in,
- .swagger-ui .response-control-media-type__title,
- .swagger-ui table thead tr td,
- .swagger-ui table thead tr th,
- .swagger-ui table tbody tr td,
- .swagger-ui .tab li,
- .swagger-ui .response-col_links,
- .swagger-ui .opblock-summary-path,
- .swagger-ui .opblock-summary-description {
- font-size: 32px !important;
- }
+ /* Common text elements */
+ .swagger-ui p,
+ .swagger-ui label,
+ .swagger-ui .btn,
+ .swagger-ui .parameter__name,
+ .swagger-ui .parameter__type,
+ .swagger-ui .parameter__in,
+ .swagger-ui .response-control-media-type__title,
+ .swagger-ui table thead tr td,
+ .swagger-ui table thead tr th,
+ .swagger-ui table tbody tr td,
+ .swagger-ui .tab li,
+ .swagger-ui .response-col_links,
+ .swagger-ui .opblock-summary-path,
+ .swagger-ui .opblock-summary-description {
+ font-size: 32px !important;
+ }
- /* Headings scale */
- .swagger-ui h1 { font-size: 1.75rem !important; }
- .swagger-ui h2 { font-size: 1.5rem !important; }
- .swagger-ui h3 { font-size: 1.25rem !important; }
- .swagger-ui h4 { font-size: 1.125rem !important; }
- .swagger-ui h5, .swagger-ui h6 { font-size: 1rem !important; }
+ /* Headings scale */
+ .swagger-ui h1 {
+ font-size: 1.75rem !important;
+ }
+
+ .swagger-ui h2 {
+ font-size: 1.5rem !important;
+ }
+
+ .swagger-ui h3 {
+ font-size: 1.25rem !important;
+ }
+
+ .swagger-ui h4 {
+ font-size: 1.125rem !important;
+ }
+
+ .swagger-ui h5,
+ .swagger-ui h6 {
+ font-size: 1rem !important;
+ }
}
diff --git a/backend/api/src/react-to-message.ts b/backend/api/src/react-to-message.ts
index c97ccfbd..56e1154f 100644
--- a/backend/api/src/react-to-message.ts
+++ b/backend/api/src/react-to-message.ts
@@ -1,9 +1,12 @@
-import {APIError, APIHandler} from './helpers/endpoint'
+import {broadcastPrivateMessages} from 'api/helpers/private-messages'
import {createSupabaseDirectClient} from 'shared/supabase/init'
-import {broadcastPrivateMessages} from "api/helpers/private-messages";
+import {APIError, APIHandler} from './helpers/endpoint'
-export const reactToMessage: APIHandler<'react-to-message'> = async ({messageId, reaction, toDelete}, auth) => {
+export const reactToMessage: APIHandler<'react-to-message'> = async (
+ {messageId, reaction, toDelete},
+ auth,
+) => {
const pg = createSupabaseDirectClient()
// Verify user is a member of the channel
@@ -13,7 +16,7 @@ export const reactToMessage: APIHandler<'react-to-message'> = async ({messageId,
JOIN private_user_messages msg ON msg.channel_id = m.channel_id
WHERE m.user_id = $1
AND msg.id = $2`,
- [auth.uid, messageId]
+ [auth.uid, messageId],
)
if (!message) {
@@ -27,7 +30,7 @@ export const reactToMessage: APIHandler<'react-to-message'> = async ({messageId,
SET reactions = reactions - $1
WHERE id = $2
AND reactions -> $1 ? $3`,
- [reaction, messageId, auth.uid]
+ [reaction, messageId, auth.uid],
)
} else {
// Toggle reaction
@@ -47,14 +50,13 @@ export const reactToMessage: APIHandler<'react-to-message'> = async ({messageId,
)
END
WHERE id = $3`,
- [reaction, auth.uid, messageId]
+ [reaction, auth.uid, messageId],
)
}
- void broadcastPrivateMessages(pg, message.channel_id, auth.uid)
- .catch((err) => {
- console.error('broadcastPrivateMessages failed', err)
- })
+ void broadcastPrivateMessages(pg, message.channel_id, auth.uid).catch((err) => {
+ console.error('broadcastPrivateMessages failed', err)
+ })
return {success: true}
}
diff --git a/backend/api/src/remove-pinned-photo.ts b/backend/api/src/remove-pinned-photo.ts
index 04880e9d..ee452300 100644
--- a/backend/api/src/remove-pinned-photo.ts
+++ b/backend/api/src/remove-pinned-photo.ts
@@ -1,23 +1,21 @@
-import { APIError } from 'api/helpers/endpoint'
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { type APIHandler } from 'api/helpers/endpoint'
-import { isAdminId } from 'common/envs/constants'
-import { log } from 'shared/utils'
-import { tryCatch } from 'common/util/try-catch'
+import {APIError, type APIHandler} from 'api/helpers/endpoint'
+import {isAdminId} from 'common/envs/constants'
+import {tryCatch} from 'common/util/try-catch'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {log} from 'shared/utils'
export const removePinnedPhoto: APIHandler<'remove-pinned-photo'> = async (
- body: { userId: string },
- auth
+ body: {userId: string},
+ auth,
) => {
- const { userId } = body
- log('remove pinned url', { userId })
+ const {userId} = body
+ log('remove pinned url', {userId})
- if (!isAdminId(auth.uid))
- throw new APIError(403, 'Only admins can remove pinned photo')
+ if (!isAdminId(auth.uid)) throw new APIError(403, 'Only admins can remove pinned photo')
const pg = createSupabaseDirectClient()
- const { error } = await tryCatch(
- pg.none('update profiles set pinned_url = null where user_id = $1', [userId])
+ const {error} = await tryCatch(
+ pg.none('update profiles set pinned_url = null where user_id = $1', [userId]),
)
if (error) {
diff --git a/backend/api/src/report.ts b/backend/api/src/report.ts
index 7d27148c..80ae9f99 100644
--- a/backend/api/src/report.ts
+++ b/backend/api/src/report.ts
@@ -1,22 +1,16 @@
-import {APIError, APIHandler} from './helpers/endpoint'
-import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {sendDiscordMessage} from 'common/discord/core'
+import {DOMAIN} from 'common/envs/constants'
+import {Row} from 'common/supabase/utils'
import {tryCatch} from 'common/util/try-catch'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
import {insert} from 'shared/supabase/utils'
-import {sendDiscordMessage} from "common/discord/core";
-import {Row} from "common/supabase/utils";
-import {DOMAIN} from "common/envs/constants";
+
+import {APIError, APIHandler} from './helpers/endpoint'
// abusable: people can report the wrong person, that didn't write the comment
// but in practice we check it manually and nothing bad happens to them automatically
export const report: APIHandler<'report'> = async (body, auth) => {
- const {
- contentOwnerId,
- contentType,
- contentId,
- description,
- parentId,
- parentType,
- } = body
+ const {contentOwnerId, contentType, contentId, description, parentId, parentType} = body
const pg = createSupabaseDirectClient()
@@ -29,7 +23,7 @@ export const report: APIHandler<'report'> = async (body, auth) => {
description,
parent_id: parentId,
parent_type: parentType,
- })
+ }),
)
if (result.error) {
@@ -39,14 +33,14 @@ export const report: APIHandler<'report'> = async (body, auth) => {
const continuation = async () => {
try {
const {data: reporter, error} = await tryCatch(
- pg.oneOrNone>('select * from users where id = $1', [auth.uid])
+ pg.oneOrNone>('select * from users where id = $1', [auth.uid]),
)
if (error) {
console.error('Failed to get user for report', error)
return
}
const {data: reported, error: userError} = await tryCatch(
- pg.oneOrNone>('select * from users where id = $1', [contentOwnerId])
+ pg.oneOrNone>('select * from users where id = $1', [contentOwnerId]),
)
if (userError) {
console.error('Failed to get reported user for report', userError)
diff --git a/backend/api/src/rsvp-event.ts b/backend/api/src/rsvp-event.ts
index 783afb58..f1d5f509 100644
--- a/backend/api/src/rsvp-event.ts
+++ b/backend/api/src/rsvp-event.ts
@@ -1,7 +1,7 @@
import {APIError, APIHandler} from 'api/helpers/endpoint'
+import {tryCatch} from 'common/util/try-catch'
import {createSupabaseDirectClient} from 'shared/supabase/init'
import {insert, update} from 'shared/supabase/utils'
-import {tryCatch} from 'common/util/try-catch'
export const rsvpEvent: APIHandler<'rsvp-event'> = async (body, auth) => {
const pg = createSupabaseDirectClient()
@@ -15,7 +15,7 @@ export const rsvpEvent: APIHandler<'rsvp-event'> = async (body, auth) => {
`SELECT id, status, max_participants
FROM events
WHERE id = $1`,
- [body.eventId]
+ [body.eventId],
)
if (!event) {
@@ -34,7 +34,7 @@ export const rsvpEvent: APIHandler<'rsvp-event'> = async (body, auth) => {
FROM events_participants
WHERE event_id = $1
AND user_id = $2`,
- [body.eventId, auth.uid]
+ [body.eventId, auth.uid],
)
if (existingRsvp) {
@@ -43,7 +43,7 @@ export const rsvpEvent: APIHandler<'rsvp-event'> = async (body, auth) => {
update(pg, 'events_participants', 'id', {
status: body.status,
id: existingRsvp.id,
- })
+ }),
)
if (error) {
@@ -52,12 +52,12 @@ export const rsvpEvent: APIHandler<'rsvp-event'> = async (body, auth) => {
} else {
// Check max participants limit
if (event.max_participants && body.status === 'going') {
- const count = await pg.one<{ count: number }>(
+ const count = await pg.one<{count: number}>(
`SELECT COUNT(*)
FROM events_participants
WHERE event_id = $1
AND status = 'going'`,
- [body.eventId]
+ [body.eventId],
)
if (Number(count.count) >= event.max_participants) {
@@ -71,7 +71,7 @@ export const rsvpEvent: APIHandler<'rsvp-event'> = async (body, auth) => {
event_id: body.eventId,
user_id: auth.uid,
status: body.status,
- })
+ }),
)
if (error) {
diff --git a/backend/api/src/save-subscription-mobile.ts b/backend/api/src/save-subscription-mobile.ts
index 68943322..717c066e 100644
--- a/backend/api/src/save-subscription-mobile.ts
+++ b/backend/api/src/save-subscription-mobile.ts
@@ -1,7 +1,11 @@
-import {APIError, APIHandler} from './helpers/endpoint'
import {createSupabaseDirectClient} from 'shared/supabase/init'
-export const saveSubscriptionMobile: APIHandler<'save-subscription-mobile'> = async (body, auth) => {
+import {APIError, APIHandler} from './helpers/endpoint'
+
+export const saveSubscriptionMobile: APIHandler<'save-subscription-mobile'> = async (
+ body,
+ auth,
+) => {
const {token} = body
if (!token) {
@@ -12,17 +16,18 @@ export const saveSubscriptionMobile: APIHandler<'save-subscription-mobile'> = as
try {
const pg = createSupabaseDirectClient()
- await pg.none(`
+ await pg.none(
+ `
insert into push_subscriptions_mobile(token, platform, user_id)
values ($1, $2, $3)
on conflict(token) do update set platform = excluded.platform,
user_id = excluded.user_id
`,
- [token, 'android', userId]
- );
- return {success: true};
+ [token, 'android', userId],
+ )
+ return {success: true}
} catch (err) {
- console.error('Error saving subscription', err);
+ console.error('Error saving subscription', err)
throw new APIError(500, `Failed to save subscription`)
}
}
diff --git a/backend/api/src/save-subscription.ts b/backend/api/src/save-subscription.ts
index 0d44b694..0a67e6a9 100644
--- a/backend/api/src/save-subscription.ts
+++ b/backend/api/src/save-subscription.ts
@@ -1,6 +1,7 @@
-import {APIError, APIHandler} from './helpers/endpoint'
import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {APIError, APIHandler} from './helpers/endpoint'
+
export const saveSubscription: APIHandler<'save-subscription'> = async (body, auth) => {
const {subscription} = body
@@ -13,29 +14,29 @@ export const saveSubscription: APIHandler<'save-subscription'> = async (body, au
try {
const pg = createSupabaseDirectClient()
// Check if a subscription already exists
- const exists = await pg.oneOrNone(
- 'select id from push_subscriptions where endpoint = $1',
- [subscription.endpoint]
- );
+ const exists = await pg.oneOrNone('select id from push_subscriptions where endpoint = $1', [
+ subscription.endpoint,
+ ])
if (exists) {
// Already exists, optionally update keys and userId
- await pg.none(
- 'update push_subscriptions set keys = $1, user_id = $2 where id = $3',
- [subscription.keys, userId, exists.id]
- );
+ await pg.none('update push_subscriptions set keys = $1, user_id = $2 where id = $3', [
+ subscription.keys,
+ userId,
+ exists.id,
+ ])
} else {
await pg.none(
`insert into push_subscriptions(endpoint, keys, user_id) values($1, $2, $3)
on conflict(endpoint) do update set keys = excluded.keys
`,
- [subscription.endpoint, subscription.keys, userId]
- );
+ [subscription.endpoint, subscription.keys, userId],
+ )
}
- return {success: true};
+ return {success: true}
} catch (err) {
- console.error('Error saving subscription', err);
+ console.error('Error saving subscription', err)
throw new APIError(500, `Failed to save subscription`)
}
}
diff --git a/backend/api/src/search-location.ts b/backend/api/src/search-location.ts
index 9779922f..0ea37fe9 100644
--- a/backend/api/src/search-location.ts
+++ b/backend/api/src/search-location.ts
@@ -1,5 +1,6 @@
+import {geodbFetch} from 'common/geodb'
+
import {APIHandler} from './helpers/endpoint'
-import {geodbFetch} from "common/geodb";
export const searchLocation: APIHandler<'search-location'> = async (body) => {
const {term, limit} = body
diff --git a/backend/api/src/search-near-city.ts b/backend/api/src/search-near-city.ts
index 046c7f8a..14f8e525 100644
--- a/backend/api/src/search-near-city.ts
+++ b/backend/api/src/search-near-city.ts
@@ -1,5 +1,6 @@
+import {geodbFetch} from 'common/geodb'
+
import {APIHandler} from './helpers/endpoint'
-import {geodbFetch} from "common/geodb";
const searchNearCityMain = async (cityId: string, radius: number) => {
const endpoint = `/cities/${cityId}/nearbyCities?radius=${radius}&offset=0&sort=-population&limit=100`
@@ -13,8 +14,6 @@ export const searchNearCity: APIHandler<'search-near-city'> = async (body) => {
export const getNearbyCities = async (cityId: string, radius: number) => {
const result = await searchNearCityMain(cityId, radius)
- const cityIds = (result.data.data as any[]).map(
- (city) => city.id.toString() as string
- )
+ const cityIds = (result.data.data as any[]).map((city) => city.id.toString() as string)
return cityIds
}
diff --git a/backend/api/src/search-users.ts b/backend/api/src/search-users.ts
index 3ecef033..00e3bc78 100644
--- a/backend/api/src/search-users.ts
+++ b/backend/api/src/search-users.ts
@@ -1,10 +1,11 @@
-import {constructPrefixTsQuery} from 'shared/helpers/search'
-import {from, limit, orderBy, renderSql, select, where,} from 'shared/supabase/sql-builder'
-import {type APIHandler} from './helpers/endpoint'
-import {convertUser} from 'common/supabase/users'
-import {createSupabaseDirectClient} from 'shared/supabase/init'
import {toUserAPIResponse} from 'common/api/user-types'
+import {convertUser} from 'common/supabase/users'
import {uniqBy} from 'lodash'
+import {constructPrefixTsQuery} from 'shared/helpers/search'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {from, limit, orderBy, renderSql, select, where} from 'shared/supabase/sql-builder'
+
+import {type APIHandler} from './helpers/endpoint'
export const searchUsers: APIHandler<'search-users'> = async (props, _auth) => {
const {term, page, limit} = props
@@ -45,19 +46,19 @@ function getSearchUserSQL(props: {
[select('*'), from('users')],
term
? [
- where(
- `name_username_vector @@ websearch_to_tsquery('english', $1)
+ where(
+ `name_username_vector @@ websearch_to_tsquery('english', $1)
or name_username_vector @@ to_tsquery('english', $2)`,
- [term, constructPrefixTsQuery(term)]
- ),
+ [term, constructPrefixTsQuery(term)],
+ ),
- orderBy(
- `ts_rank(name_username_vector, websearch_to_tsquery($1)) desc,
+ orderBy(
+ `ts_rank(name_username_vector, websearch_to_tsquery($1)) desc,
data->>'lastBetTime' desc nulls last`,
- [term]
- ),
- ]
+ [term],
+ ),
+ ]
: orderBy(`data->'creatorTraders'->'allTime' desc nulls last`),
- limit(props.limit, props.offset)
+ limit(props.limit, props.offset),
)
}
diff --git a/backend/api/src/send-search-notifications.ts b/backend/api/src/send-search-notifications.ts
index 7479a398..3451d75a 100644
--- a/backend/api/src/send-search-notifications.ts
+++ b/backend/api/src/send-search-notifications.ts
@@ -1,16 +1,15 @@
-import {createSupabaseDirectClient} from "shared/supabase/init";
-import {from, renderSql, select} from "shared/supabase/sql-builder";
-import {loadProfiles, profileQueryType} from "api/get-profiles";
-import {Row} from "common/supabase/utils";
-import {sendSearchAlertsEmail} from "email/functions/helpers";
-import {MatchesByUserType} from "common/profiles/bookmarked_searches";
-import {keyBy} from "lodash";
+import {loadProfiles, profileQueryType} from 'api/get-profiles'
+import {MatchesByUserType} from 'common/profiles/bookmarked_searches'
+import {Row} from 'common/supabase/utils'
+import {sendSearchAlertsEmail} from 'email/functions/helpers'
+import {keyBy} from 'lodash'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {from, renderSql, select} from 'shared/supabase/sql-builder'
export function convertSearchRow(row: any): any {
return row
}
-
export const notifyBookmarkedSearch = async (matches: MatchesByUserType) => {
for (const [_, value] of Object.entries(matches)) {
await sendSearchAlertsEmail(value.user, value.privateUser, value.matches)
@@ -20,40 +19,35 @@ export const notifyBookmarkedSearch = async (matches: MatchesByUserType) => {
export const sendSearchNotifications = async () => {
const pg = createSupabaseDirectClient()
- const search_query = renderSql(
- select('bookmarked_searches.*'),
- from('bookmarked_searches'),
- )
- const searches = await pg.map(search_query, [], convertSearchRow) as Row<'bookmarked_searches'>[]
+ const search_query = renderSql(select('bookmarked_searches.*'), from('bookmarked_searches'))
+ const searches = (await pg.map(
+ search_query,
+ [],
+ convertSearchRow,
+ )) as Row<'bookmarked_searches'>[]
console.debug(`Running ${searches.length} bookmarked searches`)
- const _users = await pg.map(
- renderSql(
- select('users.*'),
- from('users'),
- ),
+ const _users = (await pg.map(
+ renderSql(select('users.*'), from('users')),
[],
- convertSearchRow
- ) as Row<'users'>[]
+ convertSearchRow,
+ )) as Row<'users'>[]
const users = keyBy(_users, 'id')
console.debug('users', users)
- const _privateUsers = await pg.map(
- renderSql(
- select('private_users.*'),
- from('private_users'),
- ),
+ const _privateUsers = (await pg.map(
+ renderSql(select('private_users.*'), from('private_users')),
[],
- convertSearchRow
- ) as Row<'private_users'>[]
+ convertSearchRow,
+ )) as Row<'private_users'>[]
const privateUsers = keyBy(_privateUsers, 'id')
console.debug('privateUsers', privateUsers)
const matches: MatchesByUserType = {}
for (const row of searches) {
- if (typeof row.search_filters !== 'object') continue;
- const { orderBy: _, ...filters } = (row.search_filters ?? {}) as Record
+ if (typeof row.search_filters !== 'object') continue
+ const {orderBy: _, ...filters} = (row.search_filters ?? {}) as Record
const props = {
...filters,
skipId: row.creator_id,
@@ -85,4 +79,4 @@ export const sendSearchNotifications = async () => {
await notifyBookmarkedSearch(matches)
return {status: 'success'}
-}
\ No newline at end of file
+}
diff --git a/backend/api/src/serve.ts b/backend/api/src/serve.ts
index ed08307c..4eb47cd1 100644
--- a/backend/api/src/serve.ts
+++ b/backend/api/src/serve.ts
@@ -1,12 +1,16 @@
-import "tsconfig-paths/register";
-import * as admin from 'firebase-admin'
-import {initAdmin} from 'shared/init-admin'
+import 'tsconfig-paths/register'
+
+import {IS_LOCAL} from 'common/hosting/constants'
import {loadSecretsToEnv} from 'common/secrets'
-import {log} from 'shared/utils'
-import {IS_LOCAL} from "common/hosting/constants";
+import * as admin from 'firebase-admin'
+import {getServiceAccountCredentials} from 'shared/firebase-utils'
+import {initAdmin} from 'shared/init-admin'
import {METRIC_WRITER} from 'shared/monitoring/metric-writer'
+import {log} from 'shared/utils'
import {listen as webSocketListen} from 'shared/websockets/server'
+import {app} from './app'
+
log('Api server starting up....')
if (IS_LOCAL) {
@@ -21,13 +25,10 @@ if (IS_LOCAL) {
METRIC_WRITER.start()
-import {app} from './app'
-import {getServiceAccountCredentials} from "shared/firebase-utils";
-
const credentials = IS_LOCAL
? getServiceAccountCredentials()
: // No explicit credentials needed for deployed service.
- undefined
+ undefined
const startupProcess = async () => {
await loadSecretsToEnv(credentials)
@@ -40,4 +41,4 @@ const startupProcess = async () => {
webSocketListen(httpServer, '/ws')
}
-startupProcess().then(_r => log('Server started successfully'))
+startupProcess().then((_r) => log('Server started successfully'))
diff --git a/backend/api/src/set-compatibility-answer.ts b/backend/api/src/set-compatibility-answer.ts
index 26b43475..8a1b37eb 100644
--- a/backend/api/src/set-compatibility-answer.ts
+++ b/backend/api/src/set-compatibility-answer.ts
@@ -1,11 +1,12 @@
-import {APIHandler} from './helpers/endpoint'
-import {createSupabaseDirectClient} from 'shared/supabase/init'
import {Row} from 'common/supabase/utils'
import {recomputeCompatibilityScoresForUser} from 'shared/compatibility/compute-scores'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+
+import {APIHandler} from './helpers/endpoint'
export const setCompatibilityAnswer: APIHandler<'set-compatibility-answer'> = async (
{questionId, multipleChoice, prefChoices, importance, explanation},
- auth
+ auth,
) => {
const pg = createSupabaseDirectClient()
@@ -21,14 +22,7 @@ export const setCompatibilityAnswer: APIHandler<'set-compatibility-answer'> = as
explanation = EXCLUDED.explanation
RETURNING *
`,
- values: [
- auth.uid,
- questionId,
- multipleChoice,
- prefChoices,
- importance,
- explanation ?? null,
- ],
+ values: [auth.uid, questionId, multipleChoice, prefChoices, importance, explanation ?? null],
})
const continuation = async () => {
diff --git a/backend/api/src/set-last-online-time.ts b/backend/api/src/set-last-online-time.ts
index eae4d524..b636ed1a 100644
--- a/backend/api/src/set-last-online-time.ts
+++ b/backend/api/src/set-last-online-time.ts
@@ -1,19 +1,17 @@
-import {APIHandler} from './helpers/endpoint'
import {createSupabaseDirectClient} from 'shared/supabase/init'
-export const setLastOnlineTime: APIHandler<'set-last-online-time'> = async (
- _,
- auth
-) => {
+import {APIHandler} from './helpers/endpoint'
+
+export const setLastOnlineTime: APIHandler<'set-last-online-time'> = async (_, auth) => {
if (!auth || !auth.uid) return
await setLastOnlineTimeUser(auth.uid)
// console.log('setLastOnline')
}
-
export const setLastOnlineTimeUser = async (userId: string) => {
const pg = createSupabaseDirectClient()
- await pg.none(`
+ await pg.none(
+ `
INSERT INTO user_activity (user_id, last_online_time)
VALUES ($1, now())
ON CONFLICT (user_id)
@@ -21,6 +19,6 @@ export const setLastOnlineTimeUser = async (userId: string) => {
SET last_online_time = EXCLUDED.last_online_time
WHERE user_activity.last_online_time < now() - interval '1 minute';
`,
- [userId]
+ [userId],
)
}
diff --git a/backend/api/src/ship-profiles.ts b/backend/api/src/ship-profiles.ts
index b288f879..8c981c6d 100644
--- a/backend/api/src/ship-profiles.ts
+++ b/backend/api/src/ship-profiles.ts
@@ -1,41 +1,36 @@
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { APIError, APIHandler } from './helpers/endpoint'
-import { createProfileShipNotification } from 'shared/create-profile-notification'
-import { log } from 'shared/utils'
-import { tryCatch } from 'common/util/try-catch'
-import { insert } from 'shared/supabase/utils'
+import {tryCatch} from 'common/util/try-catch'
+import {createProfileShipNotification} from 'shared/create-profile-notification'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {insert} from 'shared/supabase/utils'
+import {log} from 'shared/utils'
+
+import {APIError, APIHandler} from './helpers/endpoint'
export const shipProfiles: APIHandler<'ship-profiles'> = async (props, auth) => {
- const { targetUserId1, targetUserId2, remove } = props
+ const {targetUserId1, targetUserId2, remove} = props
const creatorId = auth.uid
const pg = createSupabaseDirectClient()
// Check if ship already exists or with swapped target IDs
const existing = await tryCatch(
- pg.oneOrNone<{ ship_id: string }>(
+ pg.oneOrNone<{ship_id: string}>(
`select ship_id from profile_ships
where creator_id = $1
and (
target1_id = $2 and target2_id = $3
or target1_id = $3 and target2_id = $2
)`,
- [creatorId, targetUserId1, targetUserId2]
- )
+ [creatorId, targetUserId1, targetUserId2],
+ ),
)
- if (existing.error)
- throw new APIError(
- 500,
- 'Error when checking ship: ' + existing.error.message
- )
+ if (existing.error) throw new APIError(500, 'Error when checking ship: ' + existing.error.message)
if (existing.data) {
if (remove) {
- const { error } = await tryCatch(
- pg.none('delete from profile_ships where ship_id = $1', [
- existing.data.ship_id,
- ])
+ const {error} = await tryCatch(
+ pg.none('delete from profile_ships where ship_id = $1', [existing.data.ship_id]),
)
if (error) {
throw new APIError(500, 'Failed to remove ship: ' + error.message)
@@ -43,16 +38,16 @@ export const shipProfiles: APIHandler<'ship-profiles'> = async (props, auth) =>
} else {
log('Ship already exists, do nothing')
}
- return { status: 'success' }
+ return {status: 'success'}
}
// Insert the new ship
- const { data, error } = await tryCatch(
+ const {data, error} = await tryCatch(
insert(pg, 'profile_ships', {
creator_id: creatorId,
target1_id: targetUserId1,
target2_id: targetUserId2,
- })
+ }),
)
if (error) {
@@ -67,7 +62,7 @@ export const shipProfiles: APIHandler<'ship-profiles'> = async (props, auth) =>
}
return {
- result: { status: 'success' },
+ result: {status: 'success'},
continue: continuation,
}
}
diff --git a/backend/api/src/star-profile.ts b/backend/api/src/star-profile.ts
index facffe41..31aed8e8 100644
--- a/backend/api/src/star-profile.ts
+++ b/backend/api/src/star-profile.ts
@@ -1,51 +1,52 @@
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { APIError, APIHandler } from './helpers/endpoint'
-import { log } from 'shared/utils'
-import { tryCatch } from 'common/util/try-catch'
-import { Row } from 'common/supabase/utils'
-import { insert } from 'shared/supabase/utils'
+import {Row} from 'common/supabase/utils'
+import {tryCatch} from 'common/util/try-catch'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {insert} from 'shared/supabase/utils'
+import {log} from 'shared/utils'
+
+import {APIError, APIHandler} from './helpers/endpoint'
export const starProfile: APIHandler<'star-profile'> = async (props, auth) => {
- const { targetUserId, remove } = props
+ const {targetUserId, remove} = props
const creatorId = auth.uid
const pg = createSupabaseDirectClient()
if (remove) {
- const { error } = await tryCatch(
- pg.none(
- 'delete from profile_stars where creator_id = $1 and target_id = $2',
- [creatorId, targetUserId]
- )
+ const {error} = await tryCatch(
+ pg.none('delete from profile_stars where creator_id = $1 and target_id = $2', [
+ creatorId,
+ targetUserId,
+ ]),
)
if (error) {
throw new APIError(500, 'Failed to remove star: ' + error.message)
}
- return { status: 'success' }
+ return {status: 'success'}
}
// Check if star already exists
- const { data: existing } = await tryCatch(
+ const {data: existing} = await tryCatch(
pg.oneOrNone>(
'select * from profile_stars where creator_id = $1 and target_id = $2',
- [creatorId, targetUserId]
- )
+ [creatorId, targetUserId],
+ ),
)
if (existing) {
log('star already exists, do nothing')
- return { status: 'success' }
+ return {status: 'success'}
}
// Insert the new star
- const { error } = await tryCatch(
- insert(pg, 'profile_stars', { creator_id: creatorId, target_id: targetUserId })
+ const {error} = await tryCatch(
+ insert(pg, 'profile_stars', {creator_id: creatorId, target_id: targetUserId}),
)
if (error) {
throw new APIError(500, 'Failed to add star: ' + error.message)
}
- return { status: 'success' }
+ return {status: 'success'}
}
diff --git a/backend/api/src/test.ts b/backend/api/src/test.ts
index b960af34..746f4766 100644
--- a/backend/api/src/test.ts
+++ b/backend/api/src/test.ts
@@ -1,8 +1,8 @@
-import {sendTestEmail} from "email/functions/helpers";
+import {sendTestEmail} from 'email/functions/helpers'
export const localSendTestEmail = async () => {
sendTestEmail('hello@compassmeet.com')
.then(() => console.debug('Email sent successfully!'))
.catch((error) => console.error('Failed to send email:', error))
- return { message: 'Email sent successfully!'}
+ return {message: 'Email sent successfully!'}
}
diff --git a/backend/api/src/unhide-profile.ts b/backend/api/src/unhide-profile.ts
index ec57155b..68c51429 100644
--- a/backend/api/src/unhide-profile.ts
+++ b/backend/api/src/unhide-profile.ts
@@ -1,12 +1,10 @@
-import {APIHandler} from './helpers/endpoint'
import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {APIHandler} from './helpers/endpoint'
+
// Unhide a profile for the requesting user by deleting from hidden_profiles.
// Idempotent: if the pair does not exist, succeed silently.
-export const unhideProfile: APIHandler<'unhide-profile'> = async (
- {hiddenUserId},
- auth
-) => {
+export const unhideProfile: APIHandler<'unhide-profile'> = async ({hiddenUserId}, auth) => {
const pg = createSupabaseDirectClient()
await pg.none(
@@ -14,7 +12,7 @@ export const unhideProfile: APIHandler<'unhide-profile'> = async (
from hidden_profiles
where hider_user_id = $1
and hidden_user_id = $2`,
- [auth.uid, hiddenUserId]
+ [auth.uid, hiddenUserId],
)
return {status: 'success'}
diff --git a/backend/api/src/update-event.ts b/backend/api/src/update-event.ts
index 4d9e0185..05becbad 100644
--- a/backend/api/src/update-event.ts
+++ b/backend/api/src/update-event.ts
@@ -1,7 +1,7 @@
import {APIError, APIHandler} from 'api/helpers/endpoint'
+import {tryCatch} from 'common/util/try-catch'
import {createSupabaseDirectClient} from 'shared/supabase/init'
import {update} from 'shared/supabase/utils'
-import {tryCatch} from 'common/util/try-catch'
export const updateEvent: APIHandler<'update-event'> = async (body, auth) => {
const pg = createSupabaseDirectClient()
@@ -15,7 +15,7 @@ export const updateEvent: APIHandler<'update-event'> = async (body, auth) => {
`SELECT id, creator_id, status
FROM events
WHERE id = $1`,
- [body.eventId]
+ [body.eventId],
)
if (!event) {
@@ -42,7 +42,7 @@ export const updateEvent: APIHandler<'update-event'> = async (body, auth) => {
event_end_time: body.eventEndTime,
max_participants: body.maxParticipants,
id: body.eventId,
- })
+ }),
)
if (error) {
diff --git a/backend/api/src/update-me.ts b/backend/api/src/update-me.ts
index 498a88da..9df47b7a 100644
--- a/backend/api/src/update-me.ts
+++ b/backend/api/src/update-me.ts
@@ -1,14 +1,15 @@
import {toUserAPIResponse} from 'common/api/user-types'
import {RESERVED_PATHS} from 'common/envs/constants'
+import {strip} from 'common/socials'
import {cleanDisplayName, cleanUsername} from 'common/util/clean-username'
import {removeUndefinedProps} from 'common/util/object'
import {cloneDeep, mapValues} from 'lodash'
import {createSupabaseDirectClient} from 'shared/supabase/init'
-import {getUser, getUserByUsername} from 'shared/utils'
-import {APIError, APIHandler} from './helpers/endpoint'
import {updateUser} from 'shared/supabase/users'
+import {getUser, getUserByUsername} from 'shared/utils'
import {broadcastUpdatedUser} from 'shared/websockets/helpers'
-import {strip} from 'common/socials'
+
+import {APIError, APIHandler} from './helpers/endpoint'
export const updateMe: APIHandler<'me/update'> = async (props, auth) => {
const update = cloneDeep(props)
@@ -35,12 +36,9 @@ export const updateMe: APIHandler<'me/update'> = async (props, auth) => {
const {name, username, avatarUrl, link = {}, ...rest} = update
await updateUser(pg, auth.uid, removeUndefinedProps(rest))
- const stripped = mapValues(
- link,
- (value, site) => value && strip(site as any, value)
- )
+ const stripped = mapValues(link, (value, site) => value && strip(site as any, value))
- const adds = {} as { [key: string]: string }
+ const adds = {} as {[key: string]: string}
const removes = []
for (const [key, value] of Object.entries(stripped)) {
if (value === null || value === '') {
@@ -60,20 +58,26 @@ export const updateMe: APIHandler<'me/update'> = async (props, auth) => {
)
where id = $(id)
returning data -> 'link' as link`,
- {adds, removes, id: auth.uid}
+ {adds, removes, id: auth.uid},
)
newLinks = data?.link
}
if (name) {
- await pg.none(`update users
+ await pg.none(
+ `update users
set name = $1
- where id = $2`, [name, auth.uid])
+ where id = $2`,
+ [name, auth.uid],
+ )
}
if (username) {
- await pg.none(`update users
+ await pg.none(
+ `update users
set username = $1
- where id = $2`, [username, auth.uid])
+ where id = $2`,
+ [username, auth.uid],
+ )
}
if (avatarUrl) {
await updateUser(pg, auth.uid, {avatarUrl})
@@ -89,7 +93,7 @@ export const updateMe: APIHandler<'me/update'> = async (props, auth) => {
username,
avatarUrl,
link: newLinks ?? undefined,
- })
+ }),
)
}
diff --git a/backend/api/src/update-notif-setting.ts b/backend/api/src/update-notif-setting.ts
index 6a75fb9d..8b068e76 100644
--- a/backend/api/src/update-notif-setting.ts
+++ b/backend/api/src/update-notif-setting.ts
@@ -1,11 +1,12 @@
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { updatePrivateUser } from 'shared/supabase/users'
-import { type APIHandler } from './helpers/endpoint'
-import { broadcastUpdatedPrivateUser } from 'shared/websockets/helpers'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {updatePrivateUser} from 'shared/supabase/users'
+import {broadcastUpdatedPrivateUser} from 'shared/websockets/helpers'
+
+import {type APIHandler} from './helpers/endpoint'
export const updateNotifSettings: APIHandler<'update-notif-settings'> = async (
- { type, medium, enabled },
- auth
+ {type, medium, enabled},
+ auth,
) => {
const pg = createSupabaseDirectClient()
if (type === 'opt_out_all' && medium === 'mobile') {
@@ -21,7 +22,7 @@ export const updateNotifSettings: APIHandler<'update-notif-settings'> = async (
${enabled ? `|| '[$2:name]'::jsonb` : `- $2`}
)
where id = $3`,
- [type, medium, auth.uid]
+ [type, medium, auth.uid],
)
broadcastUpdatedPrivateUser(auth.uid)
}
diff --git a/backend/api/src/update-options.ts b/backend/api/src/update-options.ts
index 5f6ebbf5..2cbcc0bd 100644
--- a/backend/api/src/update-options.ts
+++ b/backend/api/src/update-options.ts
@@ -1,76 +1,79 @@
import {APIError, APIHandler} from 'api/helpers/endpoint'
+import {OPTION_TABLES} from 'common/profiles/constants'
+import {tryCatch} from 'common/util/try-catch'
import {createSupabaseDirectClient} from 'shared/supabase/init'
import {log} from 'shared/utils'
-import {tryCatch} from 'common/util/try-catch'
-import {OPTION_TABLES} from "common/profiles/constants";
-export const updateOptions: APIHandler<'update-options'> = async (
- {table, values},
- auth
-) => {
+export const updateOptions: APIHandler<'update-options'> = async ({table, values}, auth) => {
if (!OPTION_TABLES.includes(table)) throw new APIError(400, 'Invalid table')
if (!values || !Array.isArray(values)) {
throw new APIError(400, 'No ids provided')
}
- const idsWithNumbers = values.map(id => {
+ const idsWithNumbers = values.map((id) => {
const numberId = Number(id)
return isNaN(numberId) ? {isNumber: false, v: id} : {isNumber: true, v: numberId}
})
- const names: string[] = idsWithNumbers.filter(item => !item.isNumber).map(item => item.v) as string[]
- const ids: number[] = idsWithNumbers.filter(item => item.isNumber).map(item => item.v) as number[]
+ const names: string[] = idsWithNumbers
+ .filter((item) => !item.isNumber)
+ .map((item) => item.v) as string[]
+ const ids: number[] = idsWithNumbers
+ .filter((item) => item.isNumber)
+ .map((item) => item.v) as number[]
log('Updating profile options', {table, ids, names})
const pg = createSupabaseDirectClient()
- const profileIdResult = await pg.oneOrNone<{ id: number }>(
+ const profileIdResult = await pg.oneOrNone<{id: number}>(
'SELECT id FROM profiles WHERE user_id = $1',
- [auth.uid]
+ [auth.uid],
)
if (!profileIdResult) throw new APIError(404, 'Profile not found')
const profileId = profileIdResult.id
- const result = await tryCatch(pg.tx(async (t) => {
- const currentOptionsResult = await t.manyOrNone<{ id: string }>(
- `SELECT option_id as id
+ const result = await tryCatch(
+ pg.tx(async (t) => {
+ const currentOptionsResult = await t.manyOrNone<{id: string}>(
+ `SELECT option_id as id
FROM profile_${table}
WHERE profile_id = $1`,
- [profileId]
- )
- const currentOptions = currentOptionsResult.map(row => row.id)
- if (currentOptions.sort().join(',') === ids.sort().join(',') && !names?.length) {
- log(`Skipping /update-${table} because they are already the same`)
- return undefined
- }
+ [profileId],
+ )
+ const currentOptions = currentOptionsResult.map((row) => row.id)
+ if (currentOptions.sort().join(',') === ids.sort().join(',') && !names?.length) {
+ log(`Skipping /update-${table} because they are already the same`)
+ return undefined
+ }
- // Add new options
- for (const name of (names || [])) {
- const row = await t.one<{ id: number }>(
- `INSERT INTO ${table} (name, creator_id)
+ // Add new options
+ for (const name of names || []) {
+ const row = await t.one<{id: number}>(
+ `INSERT INTO ${table} (name, creator_id)
VALUES ($1, $2)
ON CONFLICT (name) DO UPDATE
SET name = ${table}.name
RETURNING id`,
- [name, auth.uid]
- )
- ids.push(row.id)
- }
+ [name, auth.uid],
+ )
+ ids.push(row.id)
+ }
- // Delete old options for this profile
- await t.none(`DELETE FROM profile_${table} WHERE profile_id = $1`, [profileId])
+ // Delete old options for this profile
+ await t.none(`DELETE FROM profile_${table} WHERE profile_id = $1`, [profileId])
- // Insert new option_ids
- if (ids.length > 0) {
- const values = ids.map((id, i) => `($1, $${i + 2})`).join(', ')
- await t.none(
- `INSERT INTO profile_${table} (profile_id, option_id) VALUES ${values}`,
- [profileId, ...ids]
- )
- }
+ // Insert new option_ids
+ if (ids.length > 0) {
+ const values = ids.map((id, i) => `($1, $${i + 2})`).join(', ')
+ await t.none(`INSERT INTO profile_${table} (profile_id, option_id) VALUES ${values}`, [
+ profileId,
+ ...ids,
+ ])
+ }
- return ids
- }))
+ return ids
+ }),
+ )
if (result.error) {
log('Error updating profile options', result.error)
@@ -79,4 +82,3 @@ export const updateOptions: APIHandler<'update-options'> = async (
return {updatedIds: result.data}
}
-
diff --git a/backend/api/src/update-private-user-message-channel.ts b/backend/api/src/update-private-user-message-channel.ts
index 584d7a1c..47d725ea 100644
--- a/backend/api/src/update-private-user-message-channel.ts
+++ b/backend/api/src/update-private-user-message-channel.ts
@@ -1,12 +1,12 @@
-import { APIError, APIHandler } from 'api/helpers/endpoint'
-import { log, getUser } from 'shared/utils'
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { millisToTs } from 'common/supabase/utils'
+import {APIError, APIHandler} from 'api/helpers/endpoint'
+import {millisToTs} from 'common/supabase/utils'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {getUser, log} from 'shared/utils'
export const updatePrivateUserMessageChannel: APIHandler<
'update-private-user-message-channel'
> = async (body, auth) => {
- const { channelId, notifyAfterTime } = body
+ const {channelId, notifyAfterTime} = body
const pg = createSupabaseDirectClient()
const user = await getUser(auth.uid)
if (!user) throw new APIError(401, 'Your account was not found')
@@ -14,10 +14,9 @@ export const updatePrivateUserMessageChannel: APIHandler<
const membershipStatus = await pg.oneOrNone(
`select status from private_user_message_channel_members
where channel_id = $1 and user_id = $2`,
- [channelId, auth.uid]
+ [channelId, auth.uid],
)
- if (!membershipStatus)
- throw new APIError(403, 'You are not authorized to this channel')
+ if (!membershipStatus) throw new APIError(403, 'You are not authorized to this channel')
log('membershipStatus ' + membershipStatus)
await pg.none(
@@ -26,8 +25,8 @@ export const updatePrivateUserMessageChannel: APIHandler<
set notify_after_time = $3
where channel_id=$1 and user_id=$2;
`,
- [channelId, auth.uid, millisToTs(notifyAfterTime)]
+ [channelId, auth.uid, millisToTs(notifyAfterTime)],
)
- return { status: 'success', channelId: Number(channelId) }
+ return {status: 'success', channelId: Number(channelId)}
}
diff --git a/backend/api/src/update-profile.ts b/backend/api/src/update-profile.ts
index 7a3d78c0..7ba1568d 100644
--- a/backend/api/src/update-profile.ts
+++ b/backend/api/src/update-profile.ts
@@ -1,32 +1,27 @@
import {APIError, APIHandler} from 'api/helpers/endpoint'
+import {trimStrings} from 'common/parsing'
+import {type Row} from 'common/supabase/utils'
+import {tryCatch} from 'common/util/try-catch'
import {removePinnedUrlFromPhotoUrls} from 'shared/profiles/parse-photos'
import {createSupabaseDirectClient} from 'shared/supabase/init'
import {updateUser} from 'shared/supabase/users'
-import {log} from 'shared/utils'
-import {tryCatch} from 'common/util/try-catch'
import {update} from 'shared/supabase/utils'
-import {type Row} from 'common/supabase/utils'
-import {trimStrings} from "common/parsing";
+import {log} from 'shared/utils'
-export const updateProfile: APIHandler<'update-profile'> = async (
- parsedBody,
- auth
-) => {
+export const updateProfile: APIHandler<'update-profile'> = async (parsedBody, auth) => {
trimStrings(parsedBody)
log('Updating profile', parsedBody)
const pg = createSupabaseDirectClient()
- const { data: existingProfile } = await tryCatch(
- pg.oneOrNone>('select * from profiles where user_id = $1', [
- auth.uid,
- ])
+ const {data: existingProfile} = await tryCatch(
+ pg.oneOrNone>('select * from profiles where user_id = $1', [auth.uid]),
)
if (!existingProfile) {
throw new APIError(404, 'Profile not found')
}
- log('Updating profile', { userId: auth.uid, parsedBody })
+ log('Updating profile', {userId: auth.uid, parsedBody})
await removePinnedUrlFromPhotoUrls(parsedBody)
@@ -34,8 +29,8 @@ export const updateProfile: APIHandler<'update-profile'> = async (
await updateUser(pg, auth.uid, {avatarUrl: parsedBody.pinned_url})
}
- const { data, error } = await tryCatch(
- update(pg, 'profiles', 'user_id', { user_id: auth.uid, ...parsedBody })
+ const {data, error} = await tryCatch(
+ update(pg, 'profiles', 'user_id', {user_id: auth.uid, ...parsedBody}),
)
if (error) {
diff --git a/backend/api/src/vote.ts b/backend/api/src/vote.ts
index 636b130e..51755265 100644
--- a/backend/api/src/vote.ts
+++ b/backend/api/src/vote.ts
@@ -1,8 +1,9 @@
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { getUser } from 'shared/utils'
-import { APIHandler, APIError } from './helpers/endpoint'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {getUser} from 'shared/utils'
-export const vote: APIHandler<'vote'> = async ({ voteId, choice, priority }, auth) => {
+import {APIError, APIHandler} from './helpers/endpoint'
+
+export const vote: APIHandler<'vote'> = async ({voteId, choice, priority}, auth) => {
const user = await getUser(auth.uid)
if (!user) throw new APIError(401, 'Your account was not found')
@@ -10,9 +11,9 @@ export const vote: APIHandler<'vote'> = async ({ voteId, choice, priority }, aut
// Map string choice to smallint (-1, 0, 1)
const choiceMap: Record = {
- 'for': 1,
- 'abstain': 0,
- 'against': -1,
+ for: 1,
+ abstain: 0,
+ against: -1,
}
const choiceVal = choiceMap[choice]
if (choiceVal === undefined) {
@@ -32,7 +33,7 @@ export const vote: APIHandler<'vote'> = async ({ voteId, choice, priority }, aut
try {
const result = await pg.one(query, [user.id, voteId, choiceVal, priority])
- return { data: result }
+ return {data: result}
} catch (e) {
throw new APIError(500, 'Error recording vote', e as any)
}
diff --git a/backend/api/tests/unit/ban-user.unit.test.ts b/backend/api/tests/unit/ban-user.unit.test.ts
index 97324763..320747fc 100644
--- a/backend/api/tests/unit/ban-user.unit.test.ts
+++ b/backend/api/tests/unit/ban-user.unit.test.ts
@@ -5,120 +5,106 @@ jest.mock('shared/supabase/users')
jest.mock('shared/analytics')
jest.mock('shared/utils')
-import { banUser } from "api/ban-user";
-import * as supabaseInit from "shared/supabase/init";
-import { throwErrorIfNotMod } from "shared/helpers/auth";
-import * as constants from "common/envs/constants";
-import * as supabaseUsers from "shared/supabase/users";
-import * as sharedAnalytics from "shared/analytics";
-import { AuthedUser } from "api/helpers/endpoint"
-
+import {banUser} from 'api/ban-user'
+import {AuthedUser} from 'api/helpers/endpoint'
+import * as constants from 'common/envs/constants'
+import * as sharedAnalytics from 'shared/analytics'
+import {throwErrorIfNotMod} from 'shared/helpers/auth'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseUsers from 'shared/supabase/users'
describe('banUser', () => {
- const mockPg = {} as any;
+ const mockPg = {} as any
- beforeEach(() => {
- jest.resetAllMocks();
+ beforeEach(() => {
+ jest.resetAllMocks()
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should ban a user successfully', async () => {
+ const mockUser = {
+ userId: '123',
+ unban: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ ;(constants.isAdminId as jest.Mock).mockReturnValue(false)
- describe('when given valid input', () => {
- it('should ban a user successfully', async () => {
- const mockUser = {
- userId: '123',
- unban: false
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
+ await banUser(mockUser, mockAuth, mockReq)
- (constants.isAdminId as jest.Mock).mockReturnValue(false);
+ expect(throwErrorIfNotMod).toBeCalledTimes(1)
+ expect(throwErrorIfNotMod).toBeCalledWith(mockAuth.uid)
+ expect(constants.isAdminId).toBeCalledTimes(1)
+ expect(constants.isAdminId).toBeCalledWith(mockUser.userId)
+ expect(sharedAnalytics.trackPublicEvent).toBeCalledTimes(1)
+ expect(sharedAnalytics.trackPublicEvent).toBeCalledWith(mockAuth.uid, 'ban user', {
+ userId: mockUser.userId,
+ })
+ expect(supabaseUsers.updateUser).toBeCalledTimes(1)
+ expect(supabaseUsers.updateUser).toBeCalledWith(mockPg, mockUser.userId, {
+ isBannedFromPosting: true,
+ })
+ })
- await banUser(mockUser, mockAuth, mockReq);
-
- expect(throwErrorIfNotMod).toBeCalledTimes(1);
- expect(throwErrorIfNotMod).toBeCalledWith(mockAuth.uid);
- expect(constants.isAdminId).toBeCalledTimes(1);
- expect(constants.isAdminId).toBeCalledWith(mockUser.userId);
- expect(sharedAnalytics.trackPublicEvent).toBeCalledTimes(1);
- expect(sharedAnalytics.trackPublicEvent).toBeCalledWith(
- mockAuth.uid,
- 'ban user',
- {userId: mockUser.userId}
- );
- expect(supabaseUsers.updateUser).toBeCalledTimes(1);
- expect(supabaseUsers.updateUser).toBeCalledWith(
- mockPg,
- mockUser.userId,
- {isBannedFromPosting: true}
- );
- });
+ it('should unban a user successfully', async () => {
+ const mockUser = {
+ userId: '123',
+ unban: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- it('should unban a user successfully', async () => {
- const mockUser = {
- userId: '123',
- unban: true
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
+ ;(constants.isAdminId as jest.Mock).mockReturnValue(false)
- (constants.isAdminId as jest.Mock).mockReturnValue(false);
+ await banUser(mockUser, mockAuth, mockReq)
- await banUser(mockUser, mockAuth, mockReq);
-
- expect(throwErrorIfNotMod).toBeCalledWith(mockAuth.uid);
- expect(constants.isAdminId).toBeCalledWith(mockUser.userId);
- expect(sharedAnalytics.trackPublicEvent).toBeCalledWith(
- mockAuth.uid,
- 'ban user',
- {userId: mockUser.userId}
- );
- expect(supabaseUsers.updateUser).toBeCalledWith(
- mockPg,
- mockUser.userId,
- {isBannedFromPosting: false}
- );
- });
- });
- describe('when an error occurs', () => {
- it('throw if the ban requester is not a mod or admin', async () => {
- const mockUser = {
- userId: '123',
- unban: false
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
-
- (throwErrorIfNotMod as jest.Mock).mockRejectedValue(
- new Error(`User ${mockAuth.uid} must be an admin or trusted to perform this action.`)
- );
+ expect(throwErrorIfNotMod).toBeCalledWith(mockAuth.uid)
+ expect(constants.isAdminId).toBeCalledWith(mockUser.userId)
+ expect(sharedAnalytics.trackPublicEvent).toBeCalledWith(mockAuth.uid, 'ban user', {
+ userId: mockUser.userId,
+ })
+ expect(supabaseUsers.updateUser).toBeCalledWith(mockPg, mockUser.userId, {
+ isBannedFromPosting: false,
+ })
+ })
+ })
+ describe('when an error occurs', () => {
+ it('throw if the ban requester is not a mod or admin', async () => {
+ const mockUser = {
+ userId: '123',
+ unban: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- await expect(banUser(mockUser, mockAuth, mockReq))
- .rejects
- .toThrowError(`User ${mockAuth.uid} must be an admin or trusted to perform this action.`);
- expect(throwErrorIfNotMod).toBeCalledWith(mockAuth.uid);
- });
+ ;(throwErrorIfNotMod as jest.Mock).mockRejectedValue(
+ new Error(`User ${mockAuth.uid} must be an admin or trusted to perform this action.`),
+ )
- it('throw if the ban target is an admin', async () => {
- const mockUser = {
- userId: '123',
- unban: false
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
+ await expect(banUser(mockUser, mockAuth, mockReq)).rejects.toThrowError(
+ `User ${mockAuth.uid} must be an admin or trusted to perform this action.`,
+ )
+ expect(throwErrorIfNotMod).toBeCalledWith(mockAuth.uid)
+ })
- (constants.isAdminId as jest.Mock).mockReturnValue(true);
+ it('throw if the ban target is an admin', async () => {
+ const mockUser = {
+ userId: '123',
+ unban: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- await expect(banUser(mockUser, mockAuth, mockReq))
- .rejects
- .toThrowError('Cannot ban admin');
- expect(throwErrorIfNotMod).toBeCalledWith(mockAuth.uid);
- expect(constants.isAdminId).toBeCalledWith(mockUser.userId);
- });
- });
-});
\ No newline at end of file
+ ;(constants.isAdminId as jest.Mock).mockReturnValue(true)
+
+ await expect(banUser(mockUser, mockAuth, mockReq)).rejects.toThrowError('Cannot ban admin')
+ expect(throwErrorIfNotMod).toBeCalledWith(mockAuth.uid)
+ expect(constants.isAdminId).toBeCalledWith(mockUser.userId)
+ })
+ })
+})
diff --git a/backend/api/tests/unit/block-user.unit.test.ts b/backend/api/tests/unit/block-user.unit.test.ts
index 87f19edb..a201aa36 100644
--- a/backend/api/tests/unit/block-user.unit.test.ts
+++ b/backend/api/tests/unit/block-user.unit.test.ts
@@ -2,110 +2,105 @@ jest.mock('shared/supabase/init')
jest.mock('shared/supabase/users')
jest.mock('shared/supabase/utils')
-import * as blockUserModule from "api/block-user";
-import { AuthedUser } from "api/helpers/endpoint";
-import * as supabaseInit from "shared/supabase/init";
-import * as supabaseUsers from "shared/supabase/users";
-import * as supabaseUtils from "shared/supabase/utils";
+import * as blockUserModule from 'api/block-user'
+import {AuthedUser} from 'api/helpers/endpoint'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseUsers from 'shared/supabase/users'
+import * as supabaseUtils from 'shared/supabase/utils'
describe('blockUser', () => {
- let mockPg: any;
+ let mockPg: any
- beforeEach(() => {
- jest.resetAllMocks()
- mockPg = {
- tx: jest.fn(async (cb) => {
- const mockTx = {};
- await cb(mockTx);
- }),
- };
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ tx: jest.fn(async (cb) => {
+ const mockTx = {}
+ await cb(mockTx)
+ }),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg)
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('block the user successfully', async () => {
+ const mockParams = {id: '123'}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- describe('when given valid input', () => {
- it('block the user successfully', async () => {
- const mockParams = { id: '123' }
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
+ await blockUserModule.blockUser(mockParams, mockAuth, mockReq)
- await blockUserModule.blockUser(mockParams, mockAuth, mockReq)
+ expect(mockPg.tx).toHaveBeenCalledTimes(1)
+ expect(supabaseUsers.updatePrivateUser).toBeCalledTimes(2)
+ expect(supabaseUsers.updatePrivateUser).toHaveBeenNthCalledWith(
+ 1,
+ expect.any(Object),
+ mockAuth.uid,
+ {blockedByUserIds: supabaseUtils.FieldVal.arrayConcat(mockParams.id)},
+ )
+ expect(supabaseUsers.updatePrivateUser).toHaveBeenNthCalledWith(
+ 2,
+ expect.any(Object),
+ mockParams.id,
+ {blockedByUserIds: supabaseUtils.FieldVal.arrayConcat(mockAuth.uid)},
+ )
+ })
+ })
+ describe('when an error occurs', () => {
+ it('throw an error if the user tries to block themselves', async () => {
+ const mockParams = {id: '123'}
+ const mockAuth = {uid: '123'} as AuthedUser
+ const mockReq = {} as any
- expect(mockPg.tx).toHaveBeenCalledTimes(1);
- expect(supabaseUsers.updatePrivateUser).toBeCalledTimes(2);
- expect(supabaseUsers.updatePrivateUser).toHaveBeenNthCalledWith(
- 1,
- expect.any(Object),
- mockAuth.uid,
- { blockedByUserIds: supabaseUtils.FieldVal.arrayConcat(mockParams.id)}
- );
- expect(supabaseUsers.updatePrivateUser).toHaveBeenNthCalledWith(
- 2,
- expect.any(Object),
- mockParams.id,
- { blockedByUserIds: supabaseUtils.FieldVal.arrayConcat(mockAuth.uid)}
- );
- });
- });
- describe('when an error occurs', () => {
- it('throw an error if the user tries to block themselves', async () => {
- const mockParams = { id: '123' }
- const mockAuth = {uid: '123'} as AuthedUser;
- const mockReq = {} as any;
-
- expect(blockUserModule.blockUser(mockParams, mockAuth, mockReq))
- .rejects
- .toThrowError('You cannot block yourself');
- });
- });
-});
+ expect(blockUserModule.blockUser(mockParams, mockAuth, mockReq)).rejects.toThrowError(
+ 'You cannot block yourself',
+ )
+ })
+ })
+})
describe('unblockUser', () => {
- let mockPg: any;
+ let mockPg: any
- beforeEach(() => {
- jest.resetAllMocks()
- mockPg = {
- tx: jest.fn(async (cb) => {
- const mockTx = {};
- await cb(mockTx);
- }),
- };
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ tx: jest.fn(async (cb) => {
+ const mockTx = {}
+ await cb(mockTx)
+ }),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg)
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should block the user successfully', async () => {
+ const mockParams = {id: '123'}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- describe('when given valid input', () => {
- it('should block the user successfully', async () => {
- const mockParams = { id: '123' }
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
+ await blockUserModule.unblockUser(mockParams, mockAuth, mockReq)
- await blockUserModule.unblockUser(mockParams, mockAuth, mockReq)
-
- expect(mockPg.tx).toHaveBeenCalledTimes(1);
- expect(supabaseUsers.updatePrivateUser).toBeCalledTimes(2);
- expect(supabaseUsers.updatePrivateUser).toHaveBeenNthCalledWith(
- 1,
- expect.any(Object),
- mockAuth.uid,
- { blockedByUserIds: supabaseUtils.FieldVal.arrayConcat(mockParams.id)}
- );
- expect(supabaseUsers.updatePrivateUser).toHaveBeenNthCalledWith(
- 2,
- expect.any(Object),
- mockParams.id,
- { blockedByUserIds: supabaseUtils.FieldVal.arrayConcat(mockAuth.uid)}
- );
- });
- });
-
-});
\ No newline at end of file
+ expect(mockPg.tx).toHaveBeenCalledTimes(1)
+ expect(supabaseUsers.updatePrivateUser).toBeCalledTimes(2)
+ expect(supabaseUsers.updatePrivateUser).toHaveBeenNthCalledWith(
+ 1,
+ expect.any(Object),
+ mockAuth.uid,
+ {blockedByUserIds: supabaseUtils.FieldVal.arrayConcat(mockParams.id)},
+ )
+ expect(supabaseUsers.updatePrivateUser).toHaveBeenNthCalledWith(
+ 2,
+ expect.any(Object),
+ mockParams.id,
+ {blockedByUserIds: supabaseUtils.FieldVal.arrayConcat(mockAuth.uid)},
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/compatible-profiles.unit.test.ts b/backend/api/tests/unit/compatible-profiles.unit.test.ts
index 4cfff7b7..c51cc4ad 100644
--- a/backend/api/tests/unit/compatible-profiles.unit.test.ts
+++ b/backend/api/tests/unit/compatible-profiles.unit.test.ts
@@ -1,41 +1,39 @@
-jest.mock('shared/supabase/init');
-
-import {getCompatibleProfiles} from "api/compatible-profiles";
-import * as supabaseInit from "shared/supabase/init";
+jest.mock('shared/supabase/init')
+import {getCompatibleProfiles} from 'api/compatible-profiles'
+import * as supabaseInit from 'shared/supabase/init'
describe('getCompatibleProfiles', () => {
- let mockPg = {} as any;
+ let mockPg = {} as any
beforeEach(() => {
- jest.resetAllMocks();
+ jest.resetAllMocks()
mockPg = {
map: jest.fn().mockResolvedValue([]),
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
afterEach(() => {
- jest.restoreAllMocks();
- });
+ jest.restoreAllMocks()
+ })
describe('when given valid input', () => {
it('should successfully get compatible profiles', async () => {
- const mockProps = '123';
- const mockScores = ["abc", { score: 0.69 }];
- const mockScoresFromEntries = {"abc": { score: 0.69 }};
+ const mockProps = '123'
+ const mockScores = ['abc', {score: 0.69}]
+ const mockScoresFromEntries = {abc: {score: 0.69}}
- (mockPg.map as jest.Mock).mockResolvedValue([mockScores]);
+ ;(mockPg.map as jest.Mock).mockResolvedValue([mockScores])
- const results = await getCompatibleProfiles(mockProps);
- const [sql, param, fn] = mockPg.map.mock.calls[0];
-
- expect(results.status).toEqual('success');
- expect(results.profileCompatibilityScores).toEqual(mockScoresFromEntries);
- expect(mockPg.map).toBeCalledTimes(1);
- expect(sql).toContain('select *');
- expect(sql).toContain('from compatibility_scores');
- expect(param).toStrictEqual([mockProps]);
- expect(fn).toEqual(expect.any(Function));
- });
- });
-});
+ const results = await getCompatibleProfiles(mockProps)
+ const [sql, param, fn] = mockPg.map.mock.calls[0]
+
+ expect(results.status).toEqual('success')
+ expect(results.profileCompatibilityScores).toEqual(mockScoresFromEntries)
+ expect(mockPg.map).toBeCalledTimes(1)
+ expect(sql).toContain('select *')
+ expect(sql).toContain('from compatibility_scores')
+ expect(param).toStrictEqual([mockProps])
+ expect(fn).toEqual(expect.any(Function))
+ })
+ })
+})
diff --git a/backend/api/tests/unit/contact.unit.test.ts b/backend/api/tests/unit/contact.unit.test.ts
index 49d3ed83..9e018250 100644
--- a/backend/api/tests/unit/contact.unit.test.ts
+++ b/backend/api/tests/unit/contact.unit.test.ts
@@ -1,30 +1,28 @@
-jest.mock('common/discord/core');
-jest.mock('shared/supabase/utils');
-jest.mock('shared/supabase/init');
-jest.mock('common/util/try-catch');
+jest.mock('common/discord/core')
+jest.mock('shared/supabase/utils')
+jest.mock('shared/supabase/init')
+jest.mock('common/util/try-catch')
-import {contact} from "api/contact";
-import * as supabaseInit from "shared/supabase/init";
-import * as supabaseUtils from "shared/supabase/utils";
-import {tryCatch} from "common/util/try-catch";
-import {sendDiscordMessage} from "common/discord/core";
-import {AuthedUser} from "api/helpers/endpoint";
+import {contact} from 'api/contact'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {sendDiscordMessage} from 'common/discord/core'
import {sqlMatch} from 'common/test-utils'
+import {tryCatch} from 'common/util/try-catch'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseUtils from 'shared/supabase/utils'
describe('contact', () => {
- let mockPg: any;
+ let mockPg: any
beforeEach(() => {
- jest.resetAllMocks();
+ jest.resetAllMocks()
mockPg = {
oneOrNone: jest.fn(),
- };
-
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
afterEach(() => {
- jest.restoreAllMocks();
- });
+ jest.restoreAllMocks()
+ })
describe('when given valid input', () => {
it('should send a discord message to the user', async () => {
@@ -37,53 +35,46 @@ describe('contact', () => {
content: [
{
type: 'text',
- text: 'Error test message'
- }
- ]
- }
- ]
+ text: 'Error test message',
+ },
+ ],
+ },
+ ],
},
- userId: '123'
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockDbUser = {name: 'Humphrey Mocker'};
- const mockReturnData = {} as any;
+ userId: '123',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockDbUser = {name: 'Humphrey Mocker'}
+ const mockReturnData = {} as any
- (tryCatch as jest.Mock).mockResolvedValue({data: mockReturnData, error: null});
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockReturnData, error: null})
+ const results = await contact(mockProps, mockAuth, mockReq)
- const results = await contact(mockProps, mockAuth, mockReq);
-
- expect(results.success).toBe(true);
- expect(results.result).toStrictEqual({});
- expect(tryCatch).toBeCalledTimes(1);
+ expect(results.success).toBe(true)
+ expect(results.result).toStrictEqual({})
+ expect(tryCatch).toBeCalledTimes(1)
expect(supabaseUtils.insert).toBeCalledTimes(1)
- expect(supabaseUtils.insert).toBeCalledWith(
- expect.any(Object),
- 'contact',
- {
- user_id: mockProps.userId,
- content: JSON.stringify(mockProps.content)
- }
- );
+ expect(supabaseUtils.insert).toBeCalledWith(expect.any(Object), 'contact', {
+ user_id: mockProps.userId,
+ content: JSON.stringify(mockProps.content),
+ })
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockDbUser)
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockDbUser);
+ await results.continue()
- await results.continue();
-
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select name from users where id = $1'),
- [mockProps.userId]
- );
- expect(sendDiscordMessage).toBeCalledTimes(1);
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(sqlMatch('select name from users where id = $1'), [
+ mockProps.userId,
+ ])
+ expect(sendDiscordMessage).toBeCalledTimes(1)
expect(sendDiscordMessage).toBeCalledWith(
expect.stringContaining(`New message from ${mockDbUser.name}`),
- 'contact'
- );
- });
- });
+ 'contact',
+ )
+ })
+ })
describe('when an error occurs', () => {
it('should throw if the insert function fails', async () => {
@@ -96,23 +87,23 @@ describe('contact', () => {
content: [
{
type: 'text',
- text: 'Error test message'
- }
- ]
- }
- ]
+ text: 'Error test message',
+ },
+ ],
+ },
+ ],
},
- userId: '123'
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
+ userId: '123',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error});
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error})
- expect(contact(mockProps, mockAuth, mockReq))
- .rejects
- .toThrowError('Failed to submit contact message');
- });
+ expect(contact(mockProps, mockAuth, mockReq)).rejects.toThrowError(
+ 'Failed to submit contact message',
+ )
+ })
it('should throw if unable to send discord message', async () => {
const mockProps = {
@@ -124,48 +115,42 @@ describe('contact', () => {
content: [
{
type: 'text',
- text: 'Error test message'
- }
- ]
- }
- ]
+ text: 'Error test message',
+ },
+ ],
+ },
+ ],
},
- userId: '123'
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockDbUser = {name: 'Humphrey Mocker'};
- const mockReturnData = {} as any;
+ userId: '123',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockDbUser = {name: 'Humphrey Mocker'}
+ const mockReturnData = {} as any
- (tryCatch as jest.Mock).mockResolvedValue({data: mockReturnData, error: null});
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockReturnData, error: null})
- const results = await contact(mockProps, mockAuth, mockReq);
+ const results = await contact(mockProps, mockAuth, mockReq)
- expect(results.success).toBe(true);
- expect(results.result).toStrictEqual({});
- expect(tryCatch).toBeCalledTimes(1);
+ expect(results.success).toBe(true)
+ expect(results.result).toStrictEqual({})
+ expect(tryCatch).toBeCalledTimes(1)
expect(supabaseUtils.insert).toBeCalledTimes(1)
- expect(supabaseUtils.insert).toBeCalledWith(
- expect.any(Object),
- 'contact',
- {
- user_id: mockProps.userId,
- content: JSON.stringify(mockProps.content)
- }
- );
+ expect(supabaseUtils.insert).toBeCalledWith(expect.any(Object), 'contact', {
+ user_id: mockProps.userId,
+ content: JSON.stringify(mockProps.content),
+ })
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockDbUser)
+ ;(sendDiscordMessage as jest.Mock).mockRejectedValue(new Error('Unable to send message'))
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockDbUser);
- (sendDiscordMessage as jest.Mock).mockRejectedValue(new Error('Unable to send message'));
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {
- });
+ await results.continue()
- await results.continue();
-
- expect(errorSpy).toBeCalledTimes(1);
+ expect(errorSpy).toBeCalledTimes(1)
expect(errorSpy).toBeCalledWith(
expect.stringContaining('Failed to send discord contact'),
- expect.objectContaining({name: 'Error'})
- );
- });
- });
-});
\ No newline at end of file
+ expect.objectContaining({name: 'Error'}),
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/create-bookmarked-search.unit.test.ts b/backend/api/tests/unit/create-bookmarked-search.unit.test.ts
index 33050410..8a2f45b2 100644
--- a/backend/api/tests/unit/create-bookmarked-search.unit.test.ts
+++ b/backend/api/tests/unit/create-bookmarked-search.unit.test.ts
@@ -1,51 +1,46 @@
-import {sqlMatch} from "common/test-utils";
-import {createBookmarkedSearch} from "api/create-bookmarked-search";
-import {AuthedUser} from "api/helpers/endpoint";
-import * as supabaseInit from "shared/supabase/init";
+import {createBookmarkedSearch} from 'api/create-bookmarked-search'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
-jest.mock('shared/supabase/init');
+jest.mock('shared/supabase/init')
describe('createBookmarkedSearch', () => {
- let mockPg: any;
+ let mockPg: any
beforeEach(() => {
- jest.resetAllMocks();
+ jest.resetAllMocks()
mockPg = {
one: jest.fn(),
- };
-
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
afterEach(() => {
- jest.restoreAllMocks();
- });
+ jest.restoreAllMocks()
+ })
describe('when given valid input', () => {
it('should insert a bookmarked search into the database', async () => {
const mockProps = {
search_filters: 'mock_search_filters',
location: 'mock_location',
- search_name: 'mock_search_name'
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockInserted = "mockInsertedReturn";
+ search_name: 'mock_search_name',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockInserted = 'mockInsertedReturn'
- (mockPg.one as jest.Mock).mockResolvedValue(mockInserted);
+ ;(mockPg.one as jest.Mock).mockResolvedValue(mockInserted)
- const result = await createBookmarkedSearch(mockProps, mockAuth, mockReq);
+ const result = await createBookmarkedSearch(mockProps, mockAuth, mockReq)
- expect(result).toBe(mockInserted);
- expect(mockPg.one).toBeCalledTimes(1);
- expect(mockPg.one).toHaveBeenCalledWith(
- sqlMatch('INSERT INTO bookmarked_searches'),
- [
- mockAuth.uid,
- mockProps.search_filters,
- mockProps.location,
- mockProps.search_name
- ]
- );
- });
- });
-});
\ No newline at end of file
+ expect(result).toBe(mockInserted)
+ expect(mockPg.one).toBeCalledTimes(1)
+ expect(mockPg.one).toHaveBeenCalledWith(sqlMatch('INSERT INTO bookmarked_searches'), [
+ mockAuth.uid,
+ mockProps.search_filters,
+ mockProps.location,
+ mockProps.search_name,
+ ])
+ })
+ })
+})
diff --git a/backend/api/tests/unit/create-comment.unit.test.ts b/backend/api/tests/unit/create-comment.unit.test.ts
index e1154eba..8b2a414a 100644
--- a/backend/api/tests/unit/create-comment.unit.test.ts
+++ b/backend/api/tests/unit/create-comment.unit.test.ts
@@ -1,415 +1,401 @@
-import {sqlMatch} from "common/test-utils";
-import * as supabaseInit from "shared/supabase/init";
-import {AuthedUser} from "api/helpers/endpoint";
-import * as sharedUtils from "shared/utils";
-import {createComment} from "api/create-comment";
-import * as notificationPrefs from "common/user-notification-preferences";
-import * as supabaseNotifications from "shared/supabase/notifications";
-import * as emailHelpers from "email/functions/helpers";
-import * as websocketHelpers from "shared/websockets/helpers";
-import {convertComment} from "common/supabase/comment";
-import {richTextToString} from "common/util/parse";
+import {createComment} from 'api/create-comment'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {convertComment} from 'common/supabase/comment'
+import {sqlMatch} from 'common/test-utils'
+import * as notificationPrefs from 'common/user-notification-preferences'
+import {richTextToString} from 'common/util/parse'
+import * as emailHelpers from 'email/functions/helpers'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseNotifications from 'shared/supabase/notifications'
+import * as sharedUtils from 'shared/utils'
+import * as websocketHelpers from 'shared/websockets/helpers'
-jest.mock('shared/supabase/init');
-jest.mock('shared/supabase/notifications');
-jest.mock('email/functions/helpers');
-jest.mock('common/supabase/comment');
-jest.mock('shared/utils');
-jest.mock('common/user-notification-preferences');
-jest.mock('shared/websockets/helpers');
+jest.mock('shared/supabase/init')
+jest.mock('shared/supabase/notifications')
+jest.mock('email/functions/helpers')
+jest.mock('common/supabase/comment')
+jest.mock('shared/utils')
+jest.mock('common/user-notification-preferences')
+jest.mock('shared/websockets/helpers')
describe('createComment', () => {
- let mockPg: any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- one: jest.fn()
- };
+ let mockPg: any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ one: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- describe('when given valid input', () => {
- it('should successfully create a comment', async () => {
- const mockUserId = {
- userId: '123',
- blockedUserIds: ['111']
- }
- const mockOnUser = {id: '123'}
- const mockCreator = {
- id: '1234',
- name: 'Mock Creator',
- username: 'mock.creator.username',
- avatarUrl: 'mock.creator.avatarurl',
- isBannedFromPosting: false
- }
- const mockContent = {
- content: {
- type: 'doc',
- content: [
- {
- type: 'paragraph',
- content: [
- {
- type: 'text',
- text: 'This is the comment text'
- }
- ]
- }
- ]
+ describe('when given valid input', () => {
+ it('should successfully create a comment', async () => {
+ const mockUserId = {
+ userId: '123',
+ blockedUserIds: ['111'],
+ }
+ const mockOnUser = {id: '123'}
+ const mockCreator = {
+ id: '1234',
+ name: 'Mock Creator',
+ username: 'mock.creator.username',
+ avatarUrl: 'mock.creator.avatarurl',
+ isBannedFromPosting: false,
+ }
+ const mockContent = {
+ content: {
+ type: 'doc',
+ content: [
+ {
+ type: 'paragraph',
+ content: [
+ {
+ type: 'text',
+ text: 'This is the comment text',
},
- userId: '123'
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockReplyToCommentId = {} as any;
- const mockComment = {id: 12};
- const mockNotificationDestination = {
- sendToBrowser: true,
- sendToMobile: false,
- sendToEmail: true
- };
- const mockProps = {
- userId: mockUserId.userId,
- content: mockContent.content,
- replyToCommentId: mockReplyToCommentId
- };
- const mockConvertCommentReturn = 'mockConverComment';
+ ],
+ },
+ ],
+ },
+ userId: '123',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockReplyToCommentId = {} as any
+ const mockComment = {id: 12}
+ const mockNotificationDestination = {
+ sendToBrowser: true,
+ sendToMobile: false,
+ sendToEmail: true,
+ }
+ const mockProps = {
+ userId: mockUserId.userId,
+ content: mockContent.content,
+ replyToCommentId: mockReplyToCommentId,
+ }
+ const mockConvertCommentReturn = 'mockConverComment'
- (sharedUtils.getUser as jest.Mock)
- .mockResolvedValueOnce(mockCreator)
- .mockResolvedValueOnce(mockOnUser);
- (sharedUtils.getPrivateUser as jest.Mock)
- .mockResolvedValueOnce(mockUserId)
- .mockResolvedValueOnce(mockOnUser);
- (mockPg.one as jest.Mock).mockResolvedValue(mockComment);
- (notificationPrefs.getNotificationDestinationsForUser as jest.Mock)
- .mockReturnValue(mockNotificationDestination);
- (convertComment as jest.Mock).mockReturnValue(mockConvertCommentReturn);
+ ;(sharedUtils.getUser as jest.Mock)
+ .mockResolvedValueOnce(mockCreator)
+ .mockResolvedValueOnce(mockOnUser)
+ ;(sharedUtils.getPrivateUser as jest.Mock)
+ .mockResolvedValueOnce(mockUserId)
+ .mockResolvedValueOnce(mockOnUser)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(mockComment)
+ ;(notificationPrefs.getNotificationDestinationsForUser as jest.Mock).mockReturnValue(
+ mockNotificationDestination,
+ )
+ ;(convertComment as jest.Mock).mockReturnValue(mockConvertCommentReturn)
- const results = await createComment(mockProps, mockAuth, mockReq);
-
- expect(results.status).toBe('success');
- expect(sharedUtils.getUser).toBeCalledTimes(2);
- expect(sharedUtils.getUser).toHaveBeenNthCalledWith(1, mockAuth.uid);
- expect(sharedUtils.getUser).toHaveBeenNthCalledWith(2, mockUserId.userId);
- expect(sharedUtils.getPrivateUser).toBeCalledTimes(2);
- expect(sharedUtils.getPrivateUser).toHaveBeenNthCalledWith(1, mockProps.userId);
- expect(sharedUtils.getPrivateUser).toHaveBeenNthCalledWith(2, mockOnUser.id);
- expect(mockPg.one).toBeCalledTimes(1);
- expect(mockPg.one).toBeCalledWith(
- sqlMatch('insert into profile_comments'),
- [
- mockCreator.id,
- mockCreator.name,
- mockCreator.username,
- mockCreator.avatarUrl,
- mockProps.userId,
- mockProps.content,
- mockProps.replyToCommentId
- ]
- );
- expect(notificationPrefs.getNotificationDestinationsForUser).toBeCalledTimes(1);
- expect(notificationPrefs.getNotificationDestinationsForUser).toBeCalledWith(mockOnUser, 'new_endorsement');
- expect(supabaseNotifications.insertNotificationToSupabase).toBeCalledTimes(1);
- expect(supabaseNotifications.insertNotificationToSupabase).toBeCalledWith(
- expect.any(Object),
- expect.any(Object)
- );
- expect(emailHelpers.sendNewEndorsementEmail).toBeCalledTimes(1);
- expect(emailHelpers.sendNewEndorsementEmail).toBeCalledWith(
- mockOnUser,
- mockCreator,
- mockOnUser,
- richTextToString(mockProps.content)
- );
- expect(websocketHelpers.broadcastUpdatedComment).toBeCalledTimes(1);
- expect(websocketHelpers.broadcastUpdatedComment).toBeCalledWith(mockConvertCommentReturn);
- });
- });
+ const results = await createComment(mockProps, mockAuth, mockReq)
- describe('when an error occurs', () => {
- it('should throw if there is no user matching the userId', async () => {
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockReplyToCommentId = {} as any;
- const mockUserId = {
- userId: '123',
- blockedUserIds: ['111']
- };
- const mockCreator = {
- id: '1234',
- name: 'Mock Creator',
- username: 'mock.creator.username',
- avatarUrl: 'mock.creator.avatarurl',
- isBannedFromPosting: false
- };
- const mockContent = {
- content: {
- type: 'doc',
- content: [
- {
- type: 'paragraph',
- content: [
- {
- type: 'text',
- text: 'This is the comment text'
- }
- ]
- }
- ]
+ expect(results.status).toBe('success')
+ expect(sharedUtils.getUser).toBeCalledTimes(2)
+ expect(sharedUtils.getUser).toHaveBeenNthCalledWith(1, mockAuth.uid)
+ expect(sharedUtils.getUser).toHaveBeenNthCalledWith(2, mockUserId.userId)
+ expect(sharedUtils.getPrivateUser).toBeCalledTimes(2)
+ expect(sharedUtils.getPrivateUser).toHaveBeenNthCalledWith(1, mockProps.userId)
+ expect(sharedUtils.getPrivateUser).toHaveBeenNthCalledWith(2, mockOnUser.id)
+ expect(mockPg.one).toBeCalledTimes(1)
+ expect(mockPg.one).toBeCalledWith(sqlMatch('insert into profile_comments'), [
+ mockCreator.id,
+ mockCreator.name,
+ mockCreator.username,
+ mockCreator.avatarUrl,
+ mockProps.userId,
+ mockProps.content,
+ mockProps.replyToCommentId,
+ ])
+ expect(notificationPrefs.getNotificationDestinationsForUser).toBeCalledTimes(1)
+ expect(notificationPrefs.getNotificationDestinationsForUser).toBeCalledWith(
+ mockOnUser,
+ 'new_endorsement',
+ )
+ expect(supabaseNotifications.insertNotificationToSupabase).toBeCalledTimes(1)
+ expect(supabaseNotifications.insertNotificationToSupabase).toBeCalledWith(
+ expect.any(Object),
+ expect.any(Object),
+ )
+ expect(emailHelpers.sendNewEndorsementEmail).toBeCalledTimes(1)
+ expect(emailHelpers.sendNewEndorsementEmail).toBeCalledWith(
+ mockOnUser,
+ mockCreator,
+ mockOnUser,
+ richTextToString(mockProps.content),
+ )
+ expect(websocketHelpers.broadcastUpdatedComment).toBeCalledTimes(1)
+ expect(websocketHelpers.broadcastUpdatedComment).toBeCalledWith(mockConvertCommentReturn)
+ })
+ })
+
+ describe('when an error occurs', () => {
+ it('should throw if there is no user matching the userId', async () => {
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockReplyToCommentId = {} as any
+ const mockUserId = {
+ userId: '123',
+ blockedUserIds: ['111'],
+ }
+ const mockCreator = {
+ id: '1234',
+ name: 'Mock Creator',
+ username: 'mock.creator.username',
+ avatarUrl: 'mock.creator.avatarurl',
+ isBannedFromPosting: false,
+ }
+ const mockContent = {
+ content: {
+ type: 'doc',
+ content: [
+ {
+ type: 'paragraph',
+ content: [
+ {
+ type: 'text',
+ text: 'This is the comment text',
},
- userId: '123'
- };
- const mockProps = {
- userId: mockUserId.userId,
- content: mockContent.content,
- replyToCommentId: mockReplyToCommentId
- };
+ ],
+ },
+ ],
+ },
+ userId: '123',
+ }
+ const mockProps = {
+ userId: mockUserId.userId,
+ content: mockContent.content,
+ replyToCommentId: mockReplyToCommentId,
+ }
- (sharedUtils.getUser as jest.Mock)
- .mockResolvedValueOnce(mockCreator)
- .mockResolvedValueOnce(false);
- (sharedUtils.getPrivateUser as jest.Mock)
- .mockResolvedValue(mockUserId);
+ ;(sharedUtils.getUser as jest.Mock)
+ .mockResolvedValueOnce(mockCreator)
+ .mockResolvedValueOnce(false)
+ ;(sharedUtils.getPrivateUser as jest.Mock).mockResolvedValue(mockUserId)
- expect(createComment( mockProps, mockAuth, mockReq ))
- .rejects
- .toThrowError('User not found');
- });
+ expect(createComment(mockProps, mockAuth, mockReq)).rejects.toThrowError('User not found')
+ })
- it('throw if there is no account associated with the authId', async () => {
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockReplyToCommentId = {} as any;
- const mockUserId = {
- userId: '123',
- blockedUserIds: ['111']
- };
- const mockContent = {
- content: {
- type: 'doc',
- content: [
- {
- type: 'paragraph',
- content: [
- {
- type: 'text',
- text: 'This is the comment text'
- }
- ]
- }
- ]
+ it('throw if there is no account associated with the authId', async () => {
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockReplyToCommentId = {} as any
+ const mockUserId = {
+ userId: '123',
+ blockedUserIds: ['111'],
+ }
+ const mockContent = {
+ content: {
+ type: 'doc',
+ content: [
+ {
+ type: 'paragraph',
+ content: [
+ {
+ type: 'text',
+ text: 'This is the comment text',
},
- userId: '123'
- };
- const mockProps = {
- userId: mockUserId.userId,
- content: mockContent.content,
- replyToCommentId: mockReplyToCommentId
- };
+ ],
+ },
+ ],
+ },
+ userId: '123',
+ }
+ const mockProps = {
+ userId: mockUserId.userId,
+ content: mockContent.content,
+ replyToCommentId: mockReplyToCommentId,
+ }
- (sharedUtils.getUser as jest.Mock)
- .mockResolvedValueOnce(null);
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValueOnce(null)
- expect(createComment( mockProps, mockAuth, mockReq ))
- .rejects
- .toThrowError('Your account was not found');
- });
+ expect(createComment(mockProps, mockAuth, mockReq)).rejects.toThrowError(
+ 'Your account was not found',
+ )
+ })
- it('throw if the account is banned from posting', async () => {
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockReplyToCommentId = {} as any;
- const mockUserId = {
- userId: '123',
- blockedUserIds: ['111']
- };
- const mockCreator = {
- id: '1234',
- name: 'Mock Creator',
- username: 'mock.creator.username',
- avatarUrl: 'mock.creator.avatarurl',
- isBannedFromPosting: true
- };
- const mockContent = {
- content: {
- type: 'doc',
- content: [
- {
- type: 'paragraph',
- content: [
- {
- type: 'text',
- text: 'This is the comment text'
- }
- ]
- }
- ]
+ it('throw if the account is banned from posting', async () => {
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockReplyToCommentId = {} as any
+ const mockUserId = {
+ userId: '123',
+ blockedUserIds: ['111'],
+ }
+ const mockCreator = {
+ id: '1234',
+ name: 'Mock Creator',
+ username: 'mock.creator.username',
+ avatarUrl: 'mock.creator.avatarurl',
+ isBannedFromPosting: true,
+ }
+ const mockContent = {
+ content: {
+ type: 'doc',
+ content: [
+ {
+ type: 'paragraph',
+ content: [
+ {
+ type: 'text',
+ text: 'This is the comment text',
},
- userId: '123'
- };
- const mockProps = {
- userId: mockUserId.userId,
- content: mockContent.content,
- replyToCommentId: mockReplyToCommentId
- };
+ ],
+ },
+ ],
+ },
+ userId: '123',
+ }
+ const mockProps = {
+ userId: mockUserId.userId,
+ content: mockContent.content,
+ replyToCommentId: mockReplyToCommentId,
+ }
- (sharedUtils.getUser as jest.Mock)
- .mockResolvedValueOnce(mockCreator);
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValueOnce(mockCreator)
- expect(createComment( mockProps, mockAuth, mockReq ))
- .rejects
- .toThrowError('You are banned');
- });
+ expect(createComment(mockProps, mockAuth, mockReq)).rejects.toThrowError('You are banned')
+ })
- it('throw if the other user is not found', async () => {
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockReplyToCommentId = {} as any;
- const mockUserId = {
- userId: '123',
- blockedUserIds: ['111']
- };
- const mockCreator = {
- id: '1234',
- name: 'Mock Creator',
- username: 'mock.creator.username',
- avatarUrl: 'mock.creator.avatarurl',
- isBannedFromPosting: false
- };
- const mockContent = {
- content: {
- type: 'doc',
- content: [
- {
- type: 'paragraph',
- content: [
- {
- type: 'text',
- text: 'This is the comment text'
- }
- ]
- }
- ]
+ it('throw if the other user is not found', async () => {
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockReplyToCommentId = {} as any
+ const mockUserId = {
+ userId: '123',
+ blockedUserIds: ['111'],
+ }
+ const mockCreator = {
+ id: '1234',
+ name: 'Mock Creator',
+ username: 'mock.creator.username',
+ avatarUrl: 'mock.creator.avatarurl',
+ isBannedFromPosting: false,
+ }
+ const mockContent = {
+ content: {
+ type: 'doc',
+ content: [
+ {
+ type: 'paragraph',
+ content: [
+ {
+ type: 'text',
+ text: 'This is the comment text',
},
- userId: '123'
- };
- const mockProps = {
- userId: mockUserId.userId,
- content: mockContent.content,
- replyToCommentId: mockReplyToCommentId
- };
+ ],
+ },
+ ],
+ },
+ userId: '123',
+ }
+ const mockProps = {
+ userId: mockUserId.userId,
+ content: mockContent.content,
+ replyToCommentId: mockReplyToCommentId,
+ }
- (sharedUtils.getUser as jest.Mock)
- .mockResolvedValueOnce(mockCreator);
- (sharedUtils.getPrivateUser as jest.Mock)
- .mockResolvedValue(null);
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValueOnce(mockCreator)
+ ;(sharedUtils.getPrivateUser as jest.Mock).mockResolvedValue(null)
- expect(createComment( mockProps, mockAuth, mockReq ))
- .rejects
- .toThrowError('Other user not found');
- });
+ expect(createComment(mockProps, mockAuth, mockReq)).rejects.toThrowError(
+ 'Other user not found',
+ )
+ })
- it('throw if the user has blocked you', async () => {
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockReplyToCommentId = {} as any;
- const mockUserId = {
- userId: '123',
- blockedUserIds: ['321']
- };
- const mockCreator = {
- id: '1234',
- name: 'Mock Creator',
- username: 'mock.creator.username',
- avatarUrl: 'mock.creator.avatarurl',
- isBannedFromPosting: false
- };
- const mockContent = {
- content: {
- type: 'doc',
- content: [
- {
- type: 'paragraph',
- content: [
- {
- type: 'text',
- text: 'This is the comment text'
- }
- ]
- }
- ]
+ it('throw if the user has blocked you', async () => {
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockReplyToCommentId = {} as any
+ const mockUserId = {
+ userId: '123',
+ blockedUserIds: ['321'],
+ }
+ const mockCreator = {
+ id: '1234',
+ name: 'Mock Creator',
+ username: 'mock.creator.username',
+ avatarUrl: 'mock.creator.avatarurl',
+ isBannedFromPosting: false,
+ }
+ const mockContent = {
+ content: {
+ type: 'doc',
+ content: [
+ {
+ type: 'paragraph',
+ content: [
+ {
+ type: 'text',
+ text: 'This is the comment text',
},
- userId: '123'
- };
- const mockProps = {
- userId: mockUserId.userId,
- content: mockContent.content,
- replyToCommentId: mockReplyToCommentId
- };
+ ],
+ },
+ ],
+ },
+ userId: '123',
+ }
+ const mockProps = {
+ userId: mockUserId.userId,
+ content: mockContent.content,
+ replyToCommentId: mockReplyToCommentId,
+ }
- (sharedUtils.getUser as jest.Mock)
- .mockResolvedValueOnce(mockCreator);
- (sharedUtils.getPrivateUser as jest.Mock)
- .mockResolvedValue(mockUserId);
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValueOnce(mockCreator)
+ ;(sharedUtils.getPrivateUser as jest.Mock).mockResolvedValue(mockUserId)
- expect(createComment( mockProps, mockAuth, mockReq ))
- .rejects
- .toThrowError('User has blocked you');
- });
+ expect(createComment(mockProps, mockAuth, mockReq)).rejects.toThrowError(
+ 'User has blocked you',
+ )
+ })
- it('throw if the comment is too long', async () => {
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockReplyToCommentId = {} as any;
- const mockUserId = {
- userId: '123',
- blockedUserIds: ['111']
- };
- const mockCreator = {
- id: '1234',
- name: 'Mock Creator',
- username: 'mock.creator.username',
- avatarUrl: 'mock.creator.avatarurl',
- isBannedFromPosting: false
- };
- const mockContent = {
- content: {
- type: 'doc',
- content: [
- {
- type: 'paragraph',
- content: [
- {
- type: 'text',
- text: 'This '.repeat(30000),
- }
- ]
- }
- ]
+ it('throw if the comment is too long', async () => {
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockReplyToCommentId = {} as any
+ const mockUserId = {
+ userId: '123',
+ blockedUserIds: ['111'],
+ }
+ const mockCreator = {
+ id: '1234',
+ name: 'Mock Creator',
+ username: 'mock.creator.username',
+ avatarUrl: 'mock.creator.avatarurl',
+ isBannedFromPosting: false,
+ }
+ const mockContent = {
+ content: {
+ type: 'doc',
+ content: [
+ {
+ type: 'paragraph',
+ content: [
+ {
+ type: 'text',
+ text: 'This '.repeat(30000),
},
- userId: '123'
- };
- const mockProps = {
- userId: mockUserId.userId,
- content: mockContent.content,
- replyToCommentId: mockReplyToCommentId
- };
+ ],
+ },
+ ],
+ },
+ userId: '123',
+ }
+ const mockProps = {
+ userId: mockUserId.userId,
+ content: mockContent.content,
+ replyToCommentId: mockReplyToCommentId,
+ }
- (sharedUtils.getUser as jest.Mock)
- .mockResolvedValueOnce(mockCreator);
- (sharedUtils.getPrivateUser as jest.Mock)
- .mockResolvedValue(mockUserId);
-
- expect(createComment( mockProps, mockAuth, mockReq ))
- .rejects
- .toThrowError('Comment is too long');
- });
- });
-});
\ No newline at end of file
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValueOnce(mockCreator)
+ ;(sharedUtils.getPrivateUser as jest.Mock).mockResolvedValue(mockUserId)
+
+ expect(createComment(mockProps, mockAuth, mockReq)).rejects.toThrowError(
+ 'Comment is too long',
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/create-compatibility-question.unit.test.ts b/backend/api/tests/unit/create-compatibility-question.unit.test.ts
index 70a69dd8..fa8283a6 100644
--- a/backend/api/tests/unit/create-compatibility-question.unit.test.ts
+++ b/backend/api/tests/unit/create-compatibility-question.unit.test.ts
@@ -1,100 +1,93 @@
-jest.mock('shared/supabase/init');
-jest.mock('shared/utils');
-jest.mock('shared/supabase/utils');
-jest.mock('common/util/try-catch');
+jest.mock('shared/supabase/init')
+jest.mock('shared/utils')
+jest.mock('shared/supabase/utils')
+jest.mock('common/util/try-catch')
-import { createCompatibilityQuestion } from "api/create-compatibility-question";
-import * as supabaseInit from "shared/supabase/init";
-import * as shareUtils from "shared/utils";
-import { tryCatch } from "common/util/try-catch";
-import * as supabaseUtils from "shared/supabase/utils";
-import { AuthedUser } from "api/helpers/endpoint";
+import {createCompatibilityQuestion} from 'api/create-compatibility-question'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {tryCatch} from 'common/util/try-catch'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseUtils from 'shared/supabase/utils'
+import * as shareUtils from 'shared/utils'
describe('createCompatibilityQuestion', () => {
- const mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
+ const mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should successfully create compatibility questions', async () => {
+ const mockQuestion = {} as any
+ const mockOptions = {} as any
+ const mockProps = {options: mockOptions, question: mockQuestion}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockCreator = {
+ id: '123',
+ }
+ const mockData = {
+ answer_type: 'mockAnswerType',
+ category: 'mockCategory',
+ created_time: 'mockCreatedTime',
+ id: 1,
+ importance_score: 1,
+ multiple_choice_options: {first_choice: 'first_answer'},
+ question: 'mockQuestion',
+ }
- describe('when given valid input', () => {
- it('should successfully create compatibility questions', async () => {
- const mockQuestion = {} as any;
- const mockOptions = {} as any;
- const mockProps = {options:mockOptions, question:mockQuestion};
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockCreator = {
- id: '123',
- };
- const mockData = {
- answer_type: "mockAnswerType",
- category: "mockCategory",
- created_time: "mockCreatedTime",
- id: 1,
- importance_score: 1,
- multiple_choice_options: {"first_choice":"first_answer"},
- question: "mockQuestion"
- };
+ ;(shareUtils.getUser as jest.Mock).mockResolvedValue(mockCreator)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockData, error: null})
- (shareUtils.getUser as jest.Mock).mockResolvedValue(mockCreator);
- (tryCatch as jest.Mock).mockResolvedValue({data:mockData, error: null});
+ const results = await createCompatibilityQuestion(mockProps, mockAuth, mockReq)
- const results = await createCompatibilityQuestion(mockProps, mockAuth, mockReq);
-
- expect(results.question).toEqual(mockData);
- expect(shareUtils.getUser).toBeCalledTimes(1);
- expect(shareUtils.getUser).toBeCalledWith(mockAuth.uid);
- expect(supabaseUtils.insert).toBeCalledTimes(1);
- expect(supabaseUtils.insert).toBeCalledWith(
- expect.any(Object),
- 'compatibility_prompts',
- {
- creator_id: mockCreator.id,
- question: mockQuestion,
- answer_type: 'compatibility_multiple_choice',
- multiple_choice_options: mockOptions
- }
- );
- });
- });
- describe('when an error occurs', () => {
- it('throws if the account does not exist', async () => {
- const mockQuestion = {} as any;
- const mockOptions = {} as any;
- const mockProps = {options:mockOptions, question:mockQuestion};
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
+ expect(results.question).toEqual(mockData)
+ expect(shareUtils.getUser).toBeCalledTimes(1)
+ expect(shareUtils.getUser).toBeCalledWith(mockAuth.uid)
+ expect(supabaseUtils.insert).toBeCalledTimes(1)
+ expect(supabaseUtils.insert).toBeCalledWith(expect.any(Object), 'compatibility_prompts', {
+ creator_id: mockCreator.id,
+ question: mockQuestion,
+ answer_type: 'compatibility_multiple_choice',
+ multiple_choice_options: mockOptions,
+ })
+ })
+ })
+ describe('when an error occurs', () => {
+ it('throws if the account does not exist', async () => {
+ const mockQuestion = {} as any
+ const mockOptions = {} as any
+ const mockProps = {options: mockOptions, question: mockQuestion}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (shareUtils.getUser as jest.Mock).mockResolvedValue(false);
-
- expect(createCompatibilityQuestion(mockProps, mockAuth, mockReq))
- .rejects
- .toThrowError('Your account was not found');
+ ;(shareUtils.getUser as jest.Mock).mockResolvedValue(false)
- });
+ expect(createCompatibilityQuestion(mockProps, mockAuth, mockReq)).rejects.toThrowError(
+ 'Your account was not found',
+ )
+ })
- it('throws if unable to create the question', async () => {
- const mockQuestion = {} as any;
- const mockOptions = {} as any;
- const mockProps = {options:mockOptions, question:mockQuestion};
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockCreator = {
- id: '123',
- };
+ it('throws if unable to create the question', async () => {
+ const mockQuestion = {} as any
+ const mockOptions = {} as any
+ const mockProps = {options: mockOptions, question: mockQuestion}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockCreator = {
+ id: '123',
+ }
- (shareUtils.getUser as jest.Mock).mockResolvedValue(mockCreator);
- (tryCatch as jest.Mock).mockResolvedValue({data:null, error: Error});
-
- expect(createCompatibilityQuestion(mockProps, mockAuth, mockReq))
- .rejects
- .toThrowError('Error creating question');
- });
- });
-});
\ No newline at end of file
+ ;(shareUtils.getUser as jest.Mock).mockResolvedValue(mockCreator)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error})
+
+ expect(createCompatibilityQuestion(mockProps, mockAuth, mockReq)).rejects.toThrowError(
+ 'Error creating question',
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/create-notification.unit.test.ts b/backend/api/tests/unit/create-notification.unit.test.ts
index 1418078b..728144f2 100644
--- a/backend/api/tests/unit/create-notification.unit.test.ts
+++ b/backend/api/tests/unit/create-notification.unit.test.ts
@@ -1,125 +1,125 @@
-jest.mock('common/util/try-catch');
-jest.mock('shared/supabase/init');
-jest.mock('shared/supabase/notifications');
+jest.mock('common/util/try-catch')
+jest.mock('shared/supabase/init')
+jest.mock('shared/supabase/notifications')
-import * as supabaseInit from "shared/supabase/init";
-import * as createNotificationModules from "api/create-notification";
-import { tryCatch } from "common/util/try-catch";
-import * as supabaseNotifications from "shared/supabase/notifications";
-import { Notification } from "common/notifications";
+import * as createNotificationModules from 'api/create-notification'
+import {Notification} from 'common/notifications'
+import {tryCatch} from 'common/util/try-catch'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseNotifications from 'shared/supabase/notifications'
describe('createNotifications', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- many: jest.fn().mockReturnValue(null)
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ many: jest.fn().mockReturnValue(null),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should sucessfully create a notification', async () => {
+ const mockUsers = [
+ {
+ created_time: 'mockCreatedTime',
+ data: {mockData: 'mockDataJson'},
+ id: 'mockId',
+ name: 'mockName',
+ name_user_vector: 'mockNUV',
+ username: 'mockUsername',
+ },
+ ]
+ const mockNotification = {
+ userId: 'mockUserId',
+ } as Notification
- describe('when given valid input', () => {
- it('should sucessfully create a notification', async () => {
- const mockUsers = [
- {
- created_time: "mockCreatedTime",
- data: {"mockData": "mockDataJson"},
- id: "mockId",
- name: "mockName",
- name_user_vector: "mockNUV",
- username: "mockUsername"
- },
- ];
- const mockNotification = {
- userId: "mockUserId"
- } as Notification;
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockUsers, error: null})
+ jest.spyOn(createNotificationModules, 'createNotification')
- (tryCatch as jest.Mock).mockResolvedValue({data: mockUsers, error:null});
- jest.spyOn(createNotificationModules, 'createNotification');
-
- const results = await createNotificationModules.createNotifications(mockNotification);
-
- expect(results?.success).toBeTruthy;
- expect(tryCatch).toBeCalledTimes(1);
- expect(mockPg.many).toBeCalledTimes(1);
- expect(mockPg.many).toBeCalledWith('select * from users');
- expect(createNotificationModules.createNotification).toBeCalledTimes(1);
- expect(createNotificationModules.createNotification).toBeCalledWith(
- mockUsers[0],
- mockNotification,
- expect.any(Object)
- );
- expect(supabaseNotifications.insertNotificationToSupabase).toBeCalledTimes(1);
- expect(supabaseNotifications.insertNotificationToSupabase).toBeCalledWith(
- mockNotification,
- expect.any(Object)
- );
- });
- });
+ const results = await createNotificationModules.createNotifications(mockNotification)
- describe('when an error occurs', () => {
- it('should throw if its unable to fetch users', async () => {
- const mockNotification = {
- userId: "mockUserId"
- } as Notification;
+ expect(results?.success).toBeTruthy()
+ expect(tryCatch).toBeCalledTimes(1)
+ expect(mockPg.many).toBeCalledTimes(1)
+ expect(mockPg.many).toBeCalledWith('select * from users')
+ expect(createNotificationModules.createNotification).toBeCalledTimes(1)
+ expect(createNotificationModules.createNotification).toBeCalledWith(
+ mockUsers[0],
+ mockNotification,
+ expect.any(Object),
+ )
+ expect(supabaseNotifications.insertNotificationToSupabase).toBeCalledTimes(1)
+ expect(supabaseNotifications.insertNotificationToSupabase).toBeCalledWith(
+ mockNotification,
+ expect.any(Object),
+ )
+ })
+ })
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ describe('when an error occurs', () => {
+ it('should throw if its unable to fetch users', async () => {
+ const mockNotification = {
+ userId: 'mockUserId',
+ } as Notification
- (tryCatch as jest.Mock).mockResolvedValue({data: null, error:Error});
-
- await createNotificationModules.createNotifications(mockNotification);
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
- expect(errorSpy).toBeCalledWith(
- 'Error fetching users',
- expect.objectContaining({name: 'Error'})
- );
- });
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error})
- it('should throw if there are no users', async () => {
- const mockNotification = {
- userId: "mockUserId"
- } as Notification;
+ await createNotificationModules.createNotifications(mockNotification)
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ expect(errorSpy).toBeCalledWith(
+ 'Error fetching users',
+ expect.objectContaining({name: 'Error'}),
+ )
+ })
- (tryCatch as jest.Mock).mockResolvedValue({data: null, error:null});
-
- await createNotificationModules.createNotifications(mockNotification);
- expect(errorSpy).toBeCalledWith('No users found');
- });
+ it('should throw if there are no users', async () => {
+ const mockNotification = {
+ userId: 'mockUserId',
+ } as Notification
- it('should throw if unable to create notification', async () => {
- const mockUsers = [
- {
- created_time: "mockCreatedTime",
- data: {"mockData": "mockDataJson"},
- id: "mockId",
- name: "mockName",
- name_user_vector: "mockNUV",
- username: "mockUsername"
- },
- ];
- const mockNotification = {
- userId: "mockUserId"
- } as Notification;
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: null, error: null})
- (tryCatch as jest.Mock).mockResolvedValue({data: mockUsers, error:null});
- jest.spyOn(createNotificationModules, 'createNotification').mockRejectedValue(new Error('Creation failure'));
-
- await createNotificationModules.createNotifications(mockNotification);
+ await createNotificationModules.createNotifications(mockNotification)
+ expect(errorSpy).toBeCalledWith('No users found')
+ })
- expect(errorSpy).toBeCalledWith(
- 'Failed to create notification',
- expect.objectContaining({name: 'Error'}),
- mockUsers[0]
- );
- });
- });
-});
\ No newline at end of file
+ it('should throw if unable to create notification', async () => {
+ const mockUsers = [
+ {
+ created_time: 'mockCreatedTime',
+ data: {mockData: 'mockDataJson'},
+ id: 'mockId',
+ name: 'mockName',
+ name_user_vector: 'mockNUV',
+ username: 'mockUsername',
+ },
+ ]
+ const mockNotification = {
+ userId: 'mockUserId',
+ } as Notification
+
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
+
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockUsers, error: null})
+ jest
+ .spyOn(createNotificationModules, 'createNotification')
+ .mockRejectedValue(new Error('Creation failure'))
+
+ await createNotificationModules.createNotifications(mockNotification)
+
+ expect(errorSpy).toBeCalledWith(
+ 'Failed to create notification',
+ expect.objectContaining({name: 'Error'}),
+ mockUsers[0],
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/create-private-user-message-channel.unit.test.ts b/backend/api/tests/unit/create-private-user-message-channel.unit.test.ts
index 32e63caa..ccf2badc 100644
--- a/backend/api/tests/unit/create-private-user-message-channel.unit.test.ts
+++ b/backend/api/tests/unit/create-private-user-message-channel.unit.test.ts
@@ -1,253 +1,250 @@
-import {sqlMatch} from "common/test-utils";
-import {createPrivateUserMessageChannel} from "api/create-private-user-message-channel";
-import * as supabaseInit from "shared/supabase/init";
-import * as sharedUtils from "shared/utils";
-import * as utilArrayModules from "common/util/array";
-import * as privateMessageModules from "api/helpers/private-messages";
-import * as admin from 'firebase-admin';
-import {AuthedUser} from "api/helpers/endpoint";
+import {createPrivateUserMessageChannel} from 'api/create-private-user-message-channel'
+import {AuthedUser} from 'api/helpers/endpoint'
+import * as privateMessageModules from 'api/helpers/private-messages'
+import {sqlMatch} from 'common/test-utils'
+import * as utilArrayModules from 'common/util/array'
+import * as admin from 'firebase-admin'
+import * as supabaseInit from 'shared/supabase/init'
+import * as sharedUtils from 'shared/utils'
-jest.mock('shared/supabase/init');
-jest.mock('common/util/array');
-jest.mock('api/helpers/private-messages');
-jest.mock('shared/utils');
+jest.mock('shared/supabase/init')
+jest.mock('common/util/array')
+jest.mock('api/helpers/private-messages')
+jest.mock('shared/utils')
jest.mock('firebase-admin', () => ({
- auth: jest.fn()
-}));
+ auth: jest.fn(),
+}))
describe('createPrivateUserMessageChannel', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- one: jest.fn(),
- none: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ one: jest.fn(),
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ ;(admin.auth as jest.Mock).mockReturnValue({
+ getUser: jest.fn().mockResolvedValue({emailVerified: true}),
+ })
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
+ describe('when given valid input', () => {
+ it('should successfully create a private user message channel (currentChannel)', async () => {
+ const mockBody = {
+ userIds: ['123'],
+ }
+ const mockUserIds = ['123', '321']
+ const mockPrivateUsers = [
+ {
+ id: '123',
+ blockedUserIds: ['111'],
+ blockedByUserIds: [],
+ },
+ {
+ id: '321',
+ blockedUserIds: ['111'],
+ blockedByUserIds: [],
+ },
+ ]
+ const mockCurrentChannel = {
+ channel_id: '444',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockCreator = {
+ isBannedFromPosting: false,
+ }
- (admin.auth as jest.Mock).mockReturnValue({
- getUser: jest.fn().mockResolvedValue({emailVerified: true})
- });
- });
- afterEach(() => {
- jest.restoreAllMocks()
- });
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator)
+ ;(utilArrayModules.filterDefined as jest.Mock).mockReturnValue(mockPrivateUsers)
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockCurrentChannel)
- describe('when given valid input', () => {
- it('should successfully create a private user message channel (currentChannel)', async () => {
- const mockBody = {
- userIds: ["123"]
- };
- const mockUserIds = ['123', '321'];
- const mockPrivateUsers = [
- {
- id: '123',
- blockedUserIds: ['111'],
- blockedByUserIds: [],
- },
- {
- id: '321',
- blockedUserIds: ['111'],
- blockedByUserIds: [],
- },
- ];
- const mockCurrentChannel = {
- channel_id: "444"
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockCreator = {
- isBannedFromPosting: false
- };
+ const results = await createPrivateUserMessageChannel(mockBody, mockAuth, mockReq)
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator);
- (utilArrayModules.filterDefined as jest.Mock).mockReturnValue(mockPrivateUsers);
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockCurrentChannel);
-
- const results = await createPrivateUserMessageChannel(mockBody, mockAuth, mockReq);
+ expect(results.status).toBe('success')
+ expect(results.channelId).toBe(444)
+ expect(sharedUtils.getUser).toBeCalledTimes(1)
+ expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid)
+ expect(sharedUtils.getPrivateUser).toBeCalledTimes(2)
+ expect(sharedUtils.getPrivateUser).toBeCalledWith(mockUserIds[0])
+ expect(sharedUtils.getPrivateUser).toBeCalledWith(mockUserIds[1])
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(
+ sqlMatch('select channel_id\n from private_user_message_channel_members'),
+ [mockUserIds],
+ )
+ })
- expect(results.status).toBe('success');
- expect(results.channelId).toBe(444);
- expect(sharedUtils.getUser).toBeCalledTimes(1);
- expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid);
- expect(sharedUtils.getPrivateUser).toBeCalledTimes(2);
- expect(sharedUtils.getPrivateUser).toBeCalledWith(mockUserIds[0]);
- expect(sharedUtils.getPrivateUser).toBeCalledWith(mockUserIds[1]);
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select channel_id\n from private_user_message_channel_members'),
- [mockUserIds]
- );
- });
+ it('should successfully create a private user message channel (channel)', async () => {
+ const mockBody = {
+ userIds: ['123'],
+ }
+ const mockUserIds = ['123', '321']
+ const mockPrivateUsers = [
+ {
+ id: '123',
+ blockedUserIds: ['111'],
+ blockedByUserIds: [],
+ },
+ {
+ id: '321',
+ blockedUserIds: ['111'],
+ blockedByUserIds: [],
+ },
+ ]
+ const mockChannel = {
+ id: '333',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockCreator = {
+ isBannedFromPosting: false,
+ }
- it('should successfully create a private user message channel (channel)', async () => {
- const mockBody = {
- userIds: ["123"]
- };
- const mockUserIds = ['123', '321'];
- const mockPrivateUsers = [
- {
- id: '123',
- blockedUserIds: ['111'],
- blockedByUserIds: [],
- },
- {
- id: '321',
- blockedUserIds: ['111'],
- blockedByUserIds: [],
- },
- ];
- const mockChannel = {
- id: "333"
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockCreator = {
- isBannedFromPosting: false
- };
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator)
+ ;(utilArrayModules.filterDefined as jest.Mock).mockReturnValue(mockPrivateUsers)
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(false)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(mockChannel)
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator);
- (utilArrayModules.filterDefined as jest.Mock).mockReturnValue(mockPrivateUsers);
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(false);
- (mockPg.one as jest.Mock).mockResolvedValue(mockChannel);
-
- const results = await createPrivateUserMessageChannel(mockBody, mockAuth, mockReq);
+ const results = await createPrivateUserMessageChannel(mockBody, mockAuth, mockReq)
- expect(results.status).toBe('success');
- expect(results.channelId).toBe(333);
- expect(sharedUtils.getUser).toBeCalledTimes(1);
- expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid);
- expect(sharedUtils.getPrivateUser).toBeCalledTimes(2);
- expect(sharedUtils.getPrivateUser).toBeCalledWith(mockUserIds[0]);
- expect(sharedUtils.getPrivateUser).toBeCalledWith(mockUserIds[1]);
- expect(mockPg.one).toBeCalledTimes(1);
- expect(mockPg.one).toBeCalledWith(
- sqlMatch('insert into private_user_message_channels default\n values\n returning id')
- );
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('insert into private_user_message_channel_members (channel_id, user_id, role, status)'),
- [mockChannel.id, mockAuth.uid]
- );
- expect(privateMessageModules.addUsersToPrivateMessageChannel).toBeCalledTimes(1);
- expect(privateMessageModules.addUsersToPrivateMessageChannel).toBeCalledWith(
- [mockUserIds[0]],
- mockChannel.id,
- expect.any(Object)
- );
- });
- });
-
- describe('when an error occurs', () => {
- it('should throw if user email is not verified', async () => {
- const mockBody = {
- userIds: ["123"]
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
+ expect(results.status).toBe('success')
+ expect(results.channelId).toBe(333)
+ expect(sharedUtils.getUser).toBeCalledTimes(1)
+ expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid)
+ expect(sharedUtils.getPrivateUser).toBeCalledTimes(2)
+ expect(sharedUtils.getPrivateUser).toBeCalledWith(mockUserIds[0])
+ expect(sharedUtils.getPrivateUser).toBeCalledWith(mockUserIds[1])
+ expect(mockPg.one).toBeCalledTimes(1)
+ expect(mockPg.one).toBeCalledWith(
+ sqlMatch(
+ 'insert into private_user_message_channels default\n values\n returning id',
+ ),
+ )
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(
+ sqlMatch(
+ 'insert into private_user_message_channel_members (channel_id, user_id, role, status)',
+ ),
+ [mockChannel.id, mockAuth.uid],
+ )
+ expect(privateMessageModules.addUsersToPrivateMessageChannel).toBeCalledTimes(1)
+ expect(privateMessageModules.addUsersToPrivateMessageChannel).toBeCalledWith(
+ [mockUserIds[0]],
+ mockChannel.id,
+ expect.any(Object),
+ )
+ })
+ })
- (admin.auth as jest.Mock).mockReturnValue({
- getUser: jest.fn().mockResolvedValue({emailVerified: false})
- });
+ describe('when an error occurs', () => {
+ it('should throw if user email is not verified', async () => {
+ const mockBody = {
+ userIds: ['123'],
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- expect(createPrivateUserMessageChannel(mockBody, mockAuth, mockReq))
- .rejects
- .toThrowError('You must verify your email to contact people.');
+ ;(admin.auth as jest.Mock).mockReturnValue({
+ getUser: jest.fn().mockResolvedValue({emailVerified: false}),
+ })
- expect(admin.auth().getUser).toHaveBeenCalledWith(mockAuth.uid);
- });
+ expect(createPrivateUserMessageChannel(mockBody, mockAuth, mockReq)).rejects.toThrowError(
+ 'You must verify your email to contact people.',
+ )
- it('should throw if the user account doesnt exist', async () => {
- const mockBody = {
- userIds: ["123"]
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
+ expect(admin.auth().getUser).toHaveBeenCalledWith(mockAuth.uid)
+ })
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
-
- expect(createPrivateUserMessageChannel(mockBody, mockAuth, mockReq))
- .rejects
- .toThrowError('Your account was not found');
- });
+ it('should throw if the user account doesnt exist', async () => {
+ const mockBody = {
+ userIds: ['123'],
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- it('should throw if the authId is banned from posting', async () => {
- const mockBody = {
- userIds: ["123"]
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockCreator = {
- isBannedFromPosting: true
- };
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator);
+ expect(createPrivateUserMessageChannel(mockBody, mockAuth, mockReq)).rejects.toThrowError(
+ 'Your account was not found',
+ )
+ })
- expect(createPrivateUserMessageChannel(mockBody, mockAuth, mockReq))
- .rejects
- .toThrowError('You are banned');
- });
+ it('should throw if the authId is banned from posting', async () => {
+ const mockBody = {
+ userIds: ['123'],
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockCreator = {
+ isBannedFromPosting: true,
+ }
- it('should throw if the array lengths dont match (privateUsers, userIds)', async () => {
- const mockBody = {
- userIds: ["123"]
- };
- const mockPrivateUsers = [
- {
- id: '123',
- blockedUserIds: ['111'],
- blockedByUserIds: [],
- },
- ];
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockCreator = {
- isBannedFromPosting: false
- };
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator)
- (sharedUtils.getUser as jest.Mock)
- .mockResolvedValue(mockCreator);
- (utilArrayModules.filterDefined as jest.Mock)
- .mockReturnValue(mockPrivateUsers);
-
- expect(createPrivateUserMessageChannel(mockBody, mockAuth, mockReq))
- .rejects
- .toThrowError(`Private user ${mockAuth.uid} not found`);
- });
+ expect(createPrivateUserMessageChannel(mockBody, mockAuth, mockReq)).rejects.toThrowError(
+ 'You are banned',
+ )
+ })
- it('should throw if there is a blocked user in the userId list', async () => {
- const mockBody = {
- userIds: ["123"]
- };
- const mockPrivateUsers = [
- {
- id: '123',
- blockedUserIds: ['111'],
- blockedByUserIds: [],
- },
- {
- id: '321',
- blockedUserIds: ['123'],
- blockedByUserIds: [],
- },
- ];
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockCreator = {
- isBannedFromPosting: false
- };
+ it('should throw if the array lengths dont match (privateUsers, userIds)', async () => {
+ const mockBody = {
+ userIds: ['123'],
+ }
+ const mockPrivateUsers = [
+ {
+ id: '123',
+ blockedUserIds: ['111'],
+ blockedByUserIds: [],
+ },
+ ]
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockCreator = {
+ isBannedFromPosting: false,
+ }
- (sharedUtils.getUser as jest.Mock)
- .mockResolvedValue(mockCreator);
- (utilArrayModules.filterDefined as jest.Mock)
- .mockReturnValue(mockPrivateUsers);
-
- expect(createPrivateUserMessageChannel(mockBody, mockAuth, mockReq))
- .rejects
- .toThrowError(`One of the users has blocked another user in the list`);
- });
- });
-});
\ No newline at end of file
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator)
+ ;(utilArrayModules.filterDefined as jest.Mock).mockReturnValue(mockPrivateUsers)
+
+ expect(createPrivateUserMessageChannel(mockBody, mockAuth, mockReq)).rejects.toThrowError(
+ `Private user ${mockAuth.uid} not found`,
+ )
+ })
+
+ it('should throw if there is a blocked user in the userId list', async () => {
+ const mockBody = {
+ userIds: ['123'],
+ }
+ const mockPrivateUsers = [
+ {
+ id: '123',
+ blockedUserIds: ['111'],
+ blockedByUserIds: [],
+ },
+ {
+ id: '321',
+ blockedUserIds: ['123'],
+ blockedByUserIds: [],
+ },
+ ]
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockCreator = {
+ isBannedFromPosting: false,
+ }
+
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator)
+ ;(utilArrayModules.filterDefined as jest.Mock).mockReturnValue(mockPrivateUsers)
+
+ expect(createPrivateUserMessageChannel(mockBody, mockAuth, mockReq)).rejects.toThrowError(
+ `One of the users has blocked another user in the list`,
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/create-private-user-message.unit.test.ts b/backend/api/tests/unit/create-private-user-message.unit.test.ts
index ca68f400..d5231f54 100644
--- a/backend/api/tests/unit/create-private-user-message.unit.test.ts
+++ b/backend/api/tests/unit/create-private-user-message.unit.test.ts
@@ -1,102 +1,100 @@
-jest.mock('shared/utils');
-jest.mock('shared/supabase/init');
-jest.mock('api/helpers/private-messages');
+jest.mock('shared/utils')
+jest.mock('shared/supabase/init')
+jest.mock('api/helpers/private-messages')
-import { createPrivateUserMessage } from "api/create-private-user-message";
-import * as sharedUtils from "shared/utils";
-import * as supabaseInit from "shared/supabase/init";
-import * as helpersPrivateMessagesModules from "api/helpers/private-messages";
-import { AuthedUser } from "api/helpers/endpoint";
-import { MAX_COMMENT_JSON_LENGTH } from "api/create-comment";
+import {MAX_COMMENT_JSON_LENGTH} from 'api/create-comment'
+import {createPrivateUserMessage} from 'api/create-private-user-message'
+import {AuthedUser} from 'api/helpers/endpoint'
+import * as helpersPrivateMessagesModules from 'api/helpers/private-messages'
+import * as supabaseInit from 'shared/supabase/init'
+import * as sharedUtils from 'shared/utils'
describe('createPrivateUserMessage', () => {
- beforeEach(() => {
- jest.resetAllMocks();
-
- const mockPg = {} as any;
+ beforeEach(() => {
+ jest.resetAllMocks()
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
+ const mockPg = {} as any
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
- describe('when given valid input', () => {
- it('successfully create a private user message', async () => {
- const mockBody = {
- content: {"": "x".repeat((MAX_COMMENT_JSON_LENGTH-8))},
- channelId: 123
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockCreator = {
- isBannedFromPosting: false
- };
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator);
+ describe('when given valid input', () => {
+ it('successfully create a private user message', async () => {
+ const mockBody = {
+ content: {'': 'x'.repeat(MAX_COMMENT_JSON_LENGTH - 8)},
+ channelId: 123,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockCreator = {
+ isBannedFromPosting: false,
+ }
- await createPrivateUserMessage(mockBody, mockAuth, mockReq);
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator)
- expect(sharedUtils.getUser).toBeCalledTimes(1);
- expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid);
- expect(helpersPrivateMessagesModules.createPrivateUserMessageMain).toBeCalledTimes(1);
- expect(helpersPrivateMessagesModules.createPrivateUserMessageMain).toBeCalledWith(
- mockCreator,
- mockBody.channelId,
- mockBody.content,
- expect.any(Object),
- 'private'
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if the content is too long', async () => {
- const mockBody = {
- content: {"": "x".repeat((MAX_COMMENT_JSON_LENGTH))},
- channelId: 123
- }
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
-
- expect(createPrivateUserMessage(mockBody, mockAuth, mockReq))
- .rejects
- .toThrowError(`Message JSON should be less than ${MAX_COMMENT_JSON_LENGTH}`);
- });
+ await createPrivateUserMessage(mockBody, mockAuth, mockReq)
- it('should throw if the user does not exist', async () => {
- const mockBody = {
- content: {"mockJson": "mockJsonContent"},
- channelId: 123
- }
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
+ expect(sharedUtils.getUser).toBeCalledTimes(1)
+ expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid)
+ expect(helpersPrivateMessagesModules.createPrivateUserMessageMain).toBeCalledTimes(1)
+ expect(helpersPrivateMessagesModules.createPrivateUserMessageMain).toBeCalledWith(
+ mockCreator,
+ mockBody.channelId,
+ mockBody.content,
+ expect.any(Object),
+ 'private',
+ )
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if the content is too long', async () => {
+ const mockBody = {
+ content: {'': 'x'.repeat(MAX_COMMENT_JSON_LENGTH)},
+ channelId: 123,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
-
- expect(createPrivateUserMessage(mockBody, mockAuth, mockReq))
- .rejects
- .toThrowError(`Your account was not found`);
- });
+ expect(createPrivateUserMessage(mockBody, mockAuth, mockReq)).rejects.toThrowError(
+ `Message JSON should be less than ${MAX_COMMENT_JSON_LENGTH}`,
+ )
+ })
- it('should throw if the user does not exist', async () => {
- const mockBody = {
- content: {"mockJson": "mockJsonContent"},
- channelId: 123
- }
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockCreator = {
- isBannedFromPosting: true
- };
+ it('should throw if the user does not exist', async () => {
+ const mockBody = {
+ content: {mockJson: 'mockJsonContent'},
+ channelId: 123,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator);
-
- expect(createPrivateUserMessage(mockBody, mockAuth, mockReq))
- .rejects
- .toThrowError(`You are banned`);
- });
- });
-});
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
+ expect(createPrivateUserMessage(mockBody, mockAuth, mockReq)).rejects.toThrowError(
+ `Your account was not found`,
+ )
+ })
+
+ it('should throw if the user does not exist', async () => {
+ const mockBody = {
+ content: {mockJson: 'mockJsonContent'},
+ channelId: 123,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockCreator = {
+ isBannedFromPosting: true,
+ }
+
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator)
+
+ expect(createPrivateUserMessage(mockBody, mockAuth, mockReq)).rejects.toThrowError(
+ `You are banned`,
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/create-profile.unit.test.ts b/backend/api/tests/unit/create-profile.unit.test.ts
index d717e843..f1b83c05 100644
--- a/backend/api/tests/unit/create-profile.unit.test.ts
+++ b/backend/api/tests/unit/create-profile.unit.test.ts
@@ -1,379 +1,370 @@
-jest.mock('shared/supabase/init');
-jest.mock('shared/utils');
-jest.mock('shared/profiles/parse-photos');
-jest.mock('shared/supabase/users');
-jest.mock('shared/supabase/utils');
-jest.mock('common/util/try-catch');
-jest.mock('shared/analytics');
-jest.mock('common/discord/core');
-jest.mock('common/util/time');
+jest.mock('shared/supabase/init')
+jest.mock('shared/utils')
+jest.mock('shared/profiles/parse-photos')
+jest.mock('shared/supabase/users')
+jest.mock('shared/supabase/utils')
+jest.mock('common/util/try-catch')
+jest.mock('shared/analytics')
+jest.mock('common/discord/core')
+jest.mock('common/util/time')
-import {createProfile} from "api/create-profile";
-import * as supabaseInit from "shared/supabase/init";
-import * as sharedUtils from "shared/utils";
-import * as supabaseUsers from "shared/supabase/users";
-import * as supabaseUtils from "shared/supabase/utils";
-import {tryCatch} from "common/util/try-catch";
-import {removePinnedUrlFromPhotoUrls} from "shared/profiles/parse-photos";
-import * as sharedAnalytics from "shared/analytics";
-import {sendDiscordMessage} from "common/discord/core";
-import {AuthedUser} from "api/helpers/endpoint";
+import {createProfile} from 'api/create-profile'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {sendDiscordMessage} from 'common/discord/core'
import {sqlMatch} from 'common/test-utils'
+import {tryCatch} from 'common/util/try-catch'
+import * as sharedAnalytics from 'shared/analytics'
+import {removePinnedUrlFromPhotoUrls} from 'shared/profiles/parse-photos'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseUsers from 'shared/supabase/users'
+import * as supabaseUtils from 'shared/supabase/utils'
+import * as sharedUtils from 'shared/utils'
describe('createProfile', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- one: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ one: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should successfully create a profile', async () => {
+ const mockBody = {
+ city: 'mockCity',
+ gender: 'mockGender',
+ looking_for_matches: true,
+ photo_urls: ['mockPhotoUrl1'],
+ pinned_url: 'mockPinnedUrl',
+ pref_gender: ['mockPrefGender'],
+ pref_relation_styles: ['mockPrefRelationStyles'],
+ visibility: 'public' as 'public' | 'member',
+ wants_kids_strength: 2,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockNProfiles = 10
+ const mockData = {
+ age: 30,
+ city: 'mockCity',
+ }
+ const mockUser = {
+ createdTime: Date.now(),
+ name: 'mockName',
+ username: 'mockUserName',
+ }
- describe('when given valid input', () => {
- it('should successfully create a profile', async () => {
- const mockBody = {
- city: "mockCity",
- gender: "mockGender",
- looking_for_matches: true,
- photo_urls: ["mockPhotoUrl1"],
- pinned_url: "mockPinnedUrl",
- pref_gender: ["mockPrefGender"],
- pref_relation_styles: ["mockPrefRelationStyles"],
- visibility: 'public' as "public" | "member",
- wants_kids_strength: 2,
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockNProfiles = 10
- const mockData = {
- age: 30,
- city: "mockCity"
- };
- const mockUser = {
- createdTime: Date.now(),
- name: "mockName",
- username: "mockUserName"
- };
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: false, error: null})
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null})
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: false, error: null});
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null});
+ const results: any = await createProfile(mockBody, mockAuth, mockReq)
- const results: any = await createProfile(mockBody, mockAuth, mockReq);
+ expect(results.result).toEqual(mockData)
+ expect(tryCatch).toBeCalledTimes(2)
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(
+ sqlMatch('select id from profiles where user_id = $1'),
+ [mockAuth.uid],
+ )
+ expect(removePinnedUrlFromPhotoUrls).toBeCalledTimes(1)
+ expect(removePinnedUrlFromPhotoUrls).toBeCalledWith(mockBody)
+ expect(sharedUtils.getUser).toBeCalledTimes(1)
+ expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid)
+ expect(supabaseUsers.updateUser).toBeCalledTimes(1)
+ expect(supabaseUsers.updateUser).toBeCalledWith(expect.any(Object), mockAuth.uid, {
+ avatarUrl: mockBody.pinned_url,
+ })
+ expect(supabaseUtils.insert).toBeCalledTimes(1)
+ expect(supabaseUtils.insert).toBeCalledWith(
+ expect.any(Object),
+ 'profiles',
+ expect.objectContaining({user_id: mockAuth.uid}),
+ )
+ ;(mockPg.one as jest.Mock).mockReturnValue(mockNProfiles)
- expect(results.result).toEqual(mockData);
- expect(tryCatch).toBeCalledTimes(2);
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select id from profiles where user_id = $1'),
- [mockAuth.uid]
- );
- expect(removePinnedUrlFromPhotoUrls).toBeCalledTimes(1);
- expect(removePinnedUrlFromPhotoUrls).toBeCalledWith(mockBody);
- expect(sharedUtils.getUser).toBeCalledTimes(1);
- expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid);
- expect(supabaseUsers.updateUser).toBeCalledTimes(1);
- expect(supabaseUsers.updateUser).toBeCalledWith(
- expect.any(Object),
- mockAuth.uid,
- {avatarUrl: mockBody.pinned_url}
- );
- expect(supabaseUtils.insert).toBeCalledTimes(1);
- expect(supabaseUtils.insert).toBeCalledWith(
- expect.any(Object),
- 'profiles',
- expect.objectContaining({user_id: mockAuth.uid})
- );
-
- (mockPg.one as jest.Mock).mockReturnValue(mockNProfiles);
+ await results.continue()
- await results.continue();
+ expect(sharedAnalytics.track).toBeCalledTimes(1)
+ expect(sharedAnalytics.track).toBeCalledWith(mockAuth.uid, 'create profile', {
+ username: mockUser.username,
+ })
+ expect(sendDiscordMessage).toBeCalledTimes(1)
+ expect(sendDiscordMessage).toBeCalledWith(
+ expect.stringContaining(mockUser.name && mockUser.username),
+ 'members',
+ )
+ })
- expect(sharedAnalytics.track).toBeCalledTimes(1);
- expect(sharedAnalytics.track).toBeCalledWith(
- mockAuth.uid,
- 'create profile',
- {username: mockUser.username}
- );
- expect(sendDiscordMessage).toBeCalledTimes(1);
- expect(sendDiscordMessage).toBeCalledWith(
- expect.stringContaining(mockUser.name && mockUser.username),
- 'members'
- );
- });
+ it('should successfully create milestone profile', async () => {
+ const mockBody = {
+ city: 'mockCity',
+ gender: 'mockGender',
+ looking_for_matches: true,
+ photo_urls: ['mockPhotoUrl1'],
+ pinned_url: 'mockPinnedUrl',
+ pref_gender: ['mockPrefGender'],
+ pref_relation_styles: ['mockPrefRelationStyles'],
+ visibility: 'public' as 'public' | 'member',
+ wants_kids_strength: 2,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockNProfiles = 15
+ const mockData = {
+ age: 30,
+ city: 'mockCity',
+ }
+ const mockUser = {
+ createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
+ name: 'mockName',
+ username: 'mockUserName',
+ }
- it('should successfully create milestone profile', async () => {
- const mockBody = {
- city: "mockCity",
- gender: "mockGender",
- looking_for_matches: true,
- photo_urls: ["mockPhotoUrl1"],
- pinned_url: "mockPinnedUrl",
- pref_gender: ["mockPrefGender"],
- pref_relation_styles: ["mockPrefRelationStyles"],
- visibility: 'public' as "public" | "member",
- wants_kids_strength: 2,
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockNProfiles = 15
- const mockData = {
- age: 30,
- city: "mockCity"
- };
- const mockUser = {
- createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
- name: "mockName",
- username: "mockUserName"
- };
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: false, error: null})
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null})
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: false, error: null});
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null});
+ const results: any = await createProfile(mockBody, mockAuth, mockReq)
- const results: any = await createProfile(mockBody, mockAuth, mockReq);
+ expect(results.result).toEqual(mockData)
+ ;(mockPg.one as jest.Mock).mockReturnValue(mockNProfiles)
- expect(results.result).toEqual(mockData);
+ await results.continue()
- (mockPg.one as jest.Mock).mockReturnValue(mockNProfiles);
+ expect(mockPg.one).toBeCalledTimes(1)
+ expect(mockPg.one).toBeCalledWith(
+ sqlMatch('SELECT count(*) FROM profiles'),
+ [],
+ expect.any(Function),
+ )
+ expect(sendDiscordMessage).toBeCalledTimes(2)
+ expect(sendDiscordMessage).toHaveBeenNthCalledWith(
+ 2,
+ expect.stringContaining(String(mockNProfiles)),
+ 'general',
+ )
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if it failed to track create profile', async () => {
+ const mockBody = {
+ city: 'mockCity',
+ gender: 'mockGender',
+ looking_for_matches: true,
+ photo_urls: ['mockPhotoUrl1'],
+ pinned_url: 'mockPinnedUrl',
+ pref_gender: ['mockPrefGender'],
+ pref_relation_styles: ['mockPrefRelationStyles'],
+ visibility: 'public' as 'public' | 'member',
+ wants_kids_strength: 2,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockData = {
+ age: 30,
+ city: 'mockCity',
+ }
+ const mockUser = {
+ createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
+ name: 'mockName',
+ username: 'mockUserName',
+ }
- await results.continue();
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: false, error: null})
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null})
- expect(mockPg.one).toBeCalledTimes(1);
- expect(mockPg.one).toBeCalledWith(
- sqlMatch('SELECT count(*) FROM profiles'),
- [],
- expect.any(Function)
- );
- expect(sendDiscordMessage).toBeCalledTimes(2);
- expect(sendDiscordMessage).toHaveBeenNthCalledWith(
- 2,
- expect.stringContaining(String(mockNProfiles)),
- 'general'
- );
+ const results: any = await createProfile(mockBody, mockAuth, mockReq)
- });
- });
- describe('when an error occurs', () => {
- it('should throw if it failed to track create profile', async () => {
- const mockBody = {
- city: "mockCity",
- gender: "mockGender",
- looking_for_matches: true,
- photo_urls: ["mockPhotoUrl1"],
- pinned_url: "mockPinnedUrl",
- pref_gender: ["mockPrefGender"],
- pref_relation_styles: ["mockPrefRelationStyles"],
- visibility: 'public' as "public" | "member",
- wants_kids_strength: 2,
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockData = {
- age: 30,
- city: "mockCity"
- };
- const mockUser = {
- createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
- name: "mockName",
- username: "mockUserName"
- };
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: false, error: null});
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null});
+ ;(sharedAnalytics.track as jest.Mock).mockRejectedValue(new Error('Track error'))
- const results: any = await createProfile(mockBody, mockAuth, mockReq);
-
- const errorSpy = jest.spyOn(console , 'error').mockImplementation(() => {});
+ await results.continue()
- (sharedAnalytics.track as jest.Mock).mockRejectedValue(new Error('Track error'));
-
- await results.continue();
+ expect(errorSpy).toBeCalledWith(
+ 'Failed to track create profile',
+ expect.objectContaining({name: 'Error'}),
+ )
+ })
- expect(errorSpy).toBeCalledWith(
- 'Failed to track create profile',
- expect.objectContaining({name: 'Error'})
- );
- });
+ it('should throw if it failed to send discord new profile', async () => {
+ const mockBody = {
+ city: 'mockCity',
+ gender: 'mockGender',
+ looking_for_matches: true,
+ photo_urls: ['mockPhotoUrl1'],
+ pinned_url: 'mockPinnedUrl',
+ pref_gender: ['mockPrefGender'],
+ pref_relation_styles: ['mockPrefRelationStyles'],
+ visibility: 'public' as 'public' | 'member',
+ wants_kids_strength: 2,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockData = {
+ age: 30,
+ city: 'mockCity',
+ }
+ const mockUser = {
+ createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
+ name: 'mockName',
+ username: 'mockUserName',
+ }
- it('should throw if it failed to send discord new profile', async () => {
- const mockBody = {
- city: "mockCity",
- gender: "mockGender",
- looking_for_matches: true,
- photo_urls: ["mockPhotoUrl1"],
- pinned_url: "mockPinnedUrl",
- pref_gender: ["mockPrefGender"],
- pref_relation_styles: ["mockPrefRelationStyles"],
- visibility: 'public' as "public" | "member",
- wants_kids_strength: 2,
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockData = {
- age: 30,
- city: "mockCity"
- };
- const mockUser = {
- createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
- name: "mockName",
- username: "mockUserName"
- };
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: null})
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null})
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: null});
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null});
+ const results: any = await createProfile(mockBody, mockAuth, mockReq)
- const results: any = await createProfile(mockBody, mockAuth, mockReq);
+ expect(results.result).toEqual(mockData)
- expect(results.result).toEqual(mockData);
-
- const errorSpy = jest.spyOn(console , 'error').mockImplementation(() => {});
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
- (sendDiscordMessage as jest.Mock).mockRejectedValue(new Error('Sending error'));
+ ;(sendDiscordMessage as jest.Mock).mockRejectedValue(new Error('Sending error'))
- await results.continue();
+ await results.continue()
- expect(errorSpy).toBeCalledWith(
- 'Failed to send discord new profile',
- expect.objectContaining({name: 'Error'})
- );
- });
+ expect(errorSpy).toBeCalledWith(
+ 'Failed to send discord new profile',
+ expect.objectContaining({name: 'Error'}),
+ )
+ })
- it('should throw if it failed to send discord user milestone', async () => {
- const mockBody = {
- city: "mockCity",
- gender: "mockGender",
- looking_for_matches: true,
- photo_urls: ["mockPhotoUrl1"],
- pinned_url: "mockPinnedUrl",
- pref_gender: ["mockPrefGender"],
- pref_relation_styles: ["mockPrefRelationStyles"],
- visibility: 'public' as "public" | "member",
- wants_kids_strength: 2,
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockNProfiles = 15
- const mockData = {
- age: 30,
- city: "mockCity"
- };
- const mockUser = {
- createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
- name: "mockName",
- username: "mockUserName"
- };
+ it('should throw if it failed to send discord user milestone', async () => {
+ const mockBody = {
+ city: 'mockCity',
+ gender: 'mockGender',
+ looking_for_matches: true,
+ photo_urls: ['mockPhotoUrl1'],
+ pinned_url: 'mockPinnedUrl',
+ pref_gender: ['mockPrefGender'],
+ pref_relation_styles: ['mockPrefRelationStyles'],
+ visibility: 'public' as 'public' | 'member',
+ wants_kids_strength: 2,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockNProfiles = 15
+ const mockData = {
+ age: 30,
+ city: 'mockCity',
+ }
+ const mockUser = {
+ createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
+ name: 'mockName',
+ username: 'mockUserName',
+ }
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: null});
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null});
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: null})
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: null})
- const results: any = await createProfile(mockBody, mockAuth, mockReq);
+ const results: any = await createProfile(mockBody, mockAuth, mockReq)
- expect(results.result).toEqual(mockData);
-
- const errorSpy = jest.spyOn(console , 'error').mockImplementation(() => {});
+ expect(results.result).toEqual(mockData)
- (sendDiscordMessage as jest.Mock)
- .mockResolvedValueOnce(null)
- .mockRejectedValueOnce(new Error('Discord error'));
- (mockPg.one as jest.Mock).mockReturnValue(mockNProfiles);
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
- await results.continue();
+ ;(sendDiscordMessage as jest.Mock)
+ .mockResolvedValueOnce(null)
+ .mockRejectedValueOnce(new Error('Discord error'))
+ ;(mockPg.one as jest.Mock).mockReturnValue(mockNProfiles)
- expect(sendDiscordMessage).toBeCalledTimes(2);
- expect(sendDiscordMessage).toHaveBeenNthCalledWith(
- 2,
- expect.stringContaining(String(mockNProfiles)),
- 'general'
- );
- expect(errorSpy).toBeCalledWith(
- 'Failed to send discord user milestone',
- expect.objectContaining({name: 'Error'})
- );
- });
+ await results.continue()
- it('should throw if the user already exists', async () => {
- const mockBody = {
- city: "mockCity",
- gender: "mockGender",
- looking_for_matches: true,
- photo_urls: ["mockPhotoUrl1"],
- pinned_url: "mockPinnedUrl",
- pref_gender: ["mockPrefGender"],
- pref_relation_styles: ["mockPrefRelationStyles"],
- visibility: 'public' as "public" | "member",
- wants_kids_strength: 2,
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
+ expect(sendDiscordMessage).toBeCalledTimes(2)
+ expect(sendDiscordMessage).toHaveBeenNthCalledWith(
+ 2,
+ expect.stringContaining(String(mockNProfiles)),
+ 'general',
+ )
+ expect(errorSpy).toBeCalledWith(
+ 'Failed to send discord user milestone',
+ expect.objectContaining({name: 'Error'}),
+ )
+ })
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: true, error: null});
+ it('should throw if the user already exists', async () => {
+ const mockBody = {
+ city: 'mockCity',
+ gender: 'mockGender',
+ looking_for_matches: true,
+ photo_urls: ['mockPhotoUrl1'],
+ pinned_url: 'mockPinnedUrl',
+ pref_gender: ['mockPrefGender'],
+ pref_relation_styles: ['mockPrefRelationStyles'],
+ visibility: 'public' as 'public' | 'member',
+ wants_kids_strength: 2,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- await expect(createProfile(mockBody, mockAuth, mockReq))
- .rejects
- .toThrowError('User already exists');
- });
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: true, error: null})
- it('should throw if unable to find the account', async () => {
- const mockBody = {
- city: "mockCity",
- gender: "mockGender",
- looking_for_matches: true,
- photo_urls: ["mockPhotoUrl1"],
- pinned_url: "mockPinnedUrl",
- pref_gender: ["mockPrefGender"],
- pref_relation_styles: ["mockPrefRelationStyles"],
- visibility: 'public' as "public" | "member",
- wants_kids_strength: 2,
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
+ await expect(createProfile(mockBody, mockAuth, mockReq)).rejects.toThrowError(
+ 'User already exists',
+ )
+ })
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: null});
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
+ it('should throw if unable to find the account', async () => {
+ const mockBody = {
+ city: 'mockCity',
+ gender: 'mockGender',
+ looking_for_matches: true,
+ photo_urls: ['mockPhotoUrl1'],
+ pinned_url: 'mockPinnedUrl',
+ pref_gender: ['mockPrefGender'],
+ pref_relation_styles: ['mockPrefRelationStyles'],
+ visibility: 'public' as 'public' | 'member',
+ wants_kids_strength: 2,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- await expect(createProfile(mockBody, mockAuth, mockReq))
- .rejects
- .toThrowError('Your account was not found');
- });
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: null})
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
- it('should throw if anything unexpected happens when creating the user', async () => {
- const mockBody = {
- city: "mockCity",
- gender: "mockGender",
- looking_for_matches: true,
- photo_urls: ["mockPhotoUrl1"],
- pinned_url: "mockPinnedUrl",
- pref_gender: ["mockPrefGender"],
- pref_relation_styles: ["mockPrefRelationStyles"],
- visibility: 'public' as "public" | "member",
- wants_kids_strength: 2,
- };
- const mockAuth = {uid: '321'} as AuthedUser;
- const mockReq = {} as any;
- const mockUser = {
- createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
- name: "mockName",
- username: "mockUserName"
- };
+ await expect(createProfile(mockBody, mockAuth, mockReq)).rejects.toThrowError(
+ 'Your account was not found',
+ )
+ })
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: null});
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
- (tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: Error});
+ it('should throw if anything unexpected happens when creating the user', async () => {
+ const mockBody = {
+ city: 'mockCity',
+ gender: 'mockGender',
+ looking_for_matches: true,
+ photo_urls: ['mockPhotoUrl1'],
+ pinned_url: 'mockPinnedUrl',
+ pref_gender: ['mockPrefGender'],
+ pref_relation_styles: ['mockPrefRelationStyles'],
+ visibility: 'public' as 'public' | 'member',
+ wants_kids_strength: 2,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockUser = {
+ createdTime: Date.now() - 2 * 60 * 60 * 1000, //2 hours ago
+ name: 'mockName',
+ username: 'mockUserName',
+ }
- await expect(createProfile(mockBody, mockAuth, mockReq))
- .rejects
- .toThrowError('Error creating user');
- });
- });
-});
\ No newline at end of file
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: null})
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: Error})
+
+ await expect(createProfile(mockBody, mockAuth, mockReq)).rejects.toThrowError(
+ 'Error creating user',
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/create-user.unit.test.ts b/backend/api/tests/unit/create-user.unit.test.ts
index 303ab80a..8ff99edd 100644
--- a/backend/api/tests/unit/create-user.unit.test.ts
+++ b/backend/api/tests/unit/create-user.unit.test.ts
@@ -1,835 +1,810 @@
-jest.mock('shared/supabase/init');
-jest.mock('shared/supabase/utils');
-jest.mock('common/supabase/users');
-jest.mock('email/functions/helpers');
-jest.mock('api/set-last-online-time');
+jest.mock('shared/supabase/init')
+jest.mock('shared/supabase/utils')
+jest.mock('common/supabase/users')
+jest.mock('email/functions/helpers')
+jest.mock('api/set-last-online-time')
jest.mock('firebase-admin', () => ({
- auth: jest.fn()
-}));
-jest.mock('shared/utils');
-jest.mock('shared/analytics');
-jest.mock('shared/firebase-utils');
-jest.mock('shared/helpers/generate-and-update-avatar-urls');
-jest.mock('common/util/object');
-jest.mock('common/user-notification-preferences');
-jest.mock('common/util/clean-username');
-jest.mock('shared/monitoring/log');
-jest.mock('common/hosting/constants');
-
-import { createUser } from "api/create-user";
-import * as supabaseInit from "shared/supabase/init";
-import * as supabaseUtils from "shared/supabase/utils";
-import * as supabaseUsers from "common/supabase/users";
-import * as emailHelpers from "email/functions/helpers";
-import * as apiSetLastTimeOnline from "api/set-last-online-time";
-import * as firebaseAdmin from "firebase-admin";
-import * as sharedUtils from "shared/utils";
-import * as sharedAnalytics from "shared/analytics";
-import * as firebaseUtils from "shared/firebase-utils";
-import * as avatarHelpers from "shared/helpers/generate-and-update-avatar-urls";
-import * as objectUtils from "common/util/object";
-import * as userNotificationPref from "common/user-notification-preferences";
-import * as usernameUtils from "common/util/clean-username";
-import * as hostingConstants from "common/hosting/constants";
-import { AuthedUser } from "api/helpers/endpoint";
+ auth: jest.fn(),
+}))
+jest.mock('shared/utils')
+jest.mock('shared/analytics')
+jest.mock('shared/firebase-utils')
+jest.mock('shared/helpers/generate-and-update-avatar-urls')
+jest.mock('common/util/object')
+jest.mock('common/user-notification-preferences')
+jest.mock('common/util/clean-username')
+jest.mock('shared/monitoring/log')
+jest.mock('common/hosting/constants')
+import {createUser} from 'api/create-user'
+import {AuthedUser} from 'api/helpers/endpoint'
+import * as apiSetLastTimeOnline from 'api/set-last-online-time'
+import * as hostingConstants from 'common/hosting/constants'
+import * as supabaseUsers from 'common/supabase/users'
+import * as userNotificationPref from 'common/user-notification-preferences'
+import * as usernameUtils from 'common/util/clean-username'
+import * as objectUtils from 'common/util/object'
+import * as emailHelpers from 'email/functions/helpers'
+import * as firebaseAdmin from 'firebase-admin'
+import * as sharedAnalytics from 'shared/analytics'
+import * as firebaseUtils from 'shared/firebase-utils'
+import * as avatarHelpers from 'shared/helpers/generate-and-update-avatar-urls'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseUtils from 'shared/supabase/utils'
+import * as sharedUtils from 'shared/utils'
describe('createUser', () => {
- const originalIsLocal = (hostingConstants as any).IS_LOCAL;
- let mockPg = {} as any;
+ const originalIsLocal = (hostingConstants as any).IS_LOCAL
+ let mockPg = {} as any
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- one: jest.fn(),
- tx: jest.fn(async (cb) => {
- const mockTx = {} as any;
- return cb(mockTx)
- })
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ one: jest.fn(),
+ tx: jest.fn(async (cb) => {
+ const mockTx = {} as any
+ return cb(mockTx)
+ }),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
- afterEach(() => {
- jest.restoreAllMocks();
- Object.defineProperty(hostingConstants, 'IS_LOCAL', {
- value: originalIsLocal,
- writable: true,
- });
- });
+ afterEach(() => {
+ jest.restoreAllMocks()
+ Object.defineProperty(hostingConstants, 'IS_LOCAL', {
+ value: originalIsLocal,
+ writable: true,
+ })
+ })
- describe('when given valid input', () => {
- it('should successfully create a user', async () => {
- Object.defineProperty(hostingConstants, 'IS_LOCAL', {
- value: false,
- writable: true
- });
- const mockProps = {
- deviceToken: "mockDeviceToken",
- adminToken: "mockAdminToken"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReferer = {
- headers: {
- 'referer': 'mockReferer'
- }
- };
- const mockReq = { get: jest.fn().mockReturnValue(mockReferer)} as any;
- const mockFirebaseUser = {
- providerData: [
- {
- providerId: 'passwords'
- }
- ],
- };
- const mockFbUser = {
- email: "mockEmail@mockServer.com",
- displayName: "mockDisplayName",
- photoURL: "mockPhotoUrl"
- };
- const mockIp = "mockIP";
- const mockBucket = {} as any;
- const mockNewUserRow = {
- created_time: "mockCreatedTime",
- data: {"mockNewUserJson": "mockNewUserJsonData"},
- id: "mockNewUserId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername"
- };
- const mockPrivateUserRow = {
- data: {"mockPrivateUserJson" : "mockPrivateUserJsonData"},
- id: "mockPrivateUserId"
- };
-
- const mockGetUser = jest.fn()
- .mockResolvedValueOnce(mockFirebaseUser)
- .mockResolvedValueOnce(mockFbUser);
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp);
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket);
- (usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (mockPg.one as jest.Mock).mockResolvedValue(0);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
- (sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false);
- (userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null);
- (supabaseUtils.insert as jest.Mock)
- .mockResolvedValueOnce(null)
- .mockResolvedValueOnce(null);
- (supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow);
- (supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow);
-
- const results: any = await createUser(mockProps, mockAuth, mockReq);
+ describe('when given valid input', () => {
+ it('should successfully create a user', async () => {
+ Object.defineProperty(hostingConstants, 'IS_LOCAL', {
+ value: false,
+ writable: true,
+ })
+ const mockProps = {
+ deviceToken: 'mockDeviceToken',
+ adminToken: 'mockAdminToken',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReferer = {
+ headers: {
+ referer: 'mockReferer',
+ },
+ }
+ const mockReq = {get: jest.fn().mockReturnValue(mockReferer)} as any
+ const mockFirebaseUser = {
+ providerData: [
+ {
+ providerId: 'passwords',
+ },
+ ],
+ }
+ const mockFbUser = {
+ email: 'mockEmail@mockServer.com',
+ displayName: 'mockDisplayName',
+ photoURL: 'mockPhotoUrl',
+ }
+ const mockIp = 'mockIP'
+ const mockBucket = {} as any
+ const mockNewUserRow = {
+ created_time: 'mockCreatedTime',
+ data: {mockNewUserJson: 'mockNewUserJsonData'},
+ id: 'mockNewUserId',
+ name: 'mockName',
+ name_username_vector: 'mockNameUsernameVector',
+ username: 'mockUsername',
+ }
+ const mockPrivateUserRow = {
+ data: {mockPrivateUserJson: 'mockPrivateUserJsonData'},
+ id: 'mockPrivateUserId',
+ }
- expect(results.result.user).toEqual(mockNewUserRow);
- expect(results.result.privateUser).toEqual(mockPrivateUserRow);
- expect(mockGetUser).toBeCalledTimes(2);
- expect(mockGetUser).toHaveBeenNthCalledWith(1, mockAuth.uid);
- expect(mockReq.get).toBeCalledTimes(1);
- expect(mockReq.get).toBeCalledWith(Object.keys(mockReferer.headers)[0]);
- expect(sharedAnalytics.getIp).toBeCalledTimes(1);
- expect(sharedAnalytics.getIp).toBeCalledWith(mockReq);
- expect(mockGetUser).toHaveBeenNthCalledWith(2, mockAuth.uid);
- expect(usernameUtils.cleanDisplayName).toBeCalledTimes(1);
- expect(usernameUtils.cleanDisplayName).toHaveBeenCalledWith(mockFbUser.displayName);
- expect(usernameUtils.cleanUsername).toBeCalledTimes(1);
- expect(usernameUtils.cleanUsername).toBeCalledWith(mockFbUser.displayName);
- expect(mockPg.one).toBeCalledTimes(1);
- expect(mockPg.tx).toBeCalledTimes(1);
- expect(sharedUtils.getUser).toBeCalledTimes(1);
- expect(sharedUtils.getUser).toHaveBeenCalledWith(
- mockAuth.uid,
- expect.any(Object)
- );
- expect(userNotificationPref.getDefaultNotificationPreferences).toBeCalledTimes(1);
- expect(supabaseUtils.insert).toBeCalledTimes(2);
- expect(supabaseUtils.insert).toHaveBeenNthCalledWith(
- 1,
- expect.any(Object),
- 'users',
- expect.objectContaining(
- {
- id: mockAuth.uid,
- name: mockFbUser.displayName,
- username: mockFbUser.displayName,
- }
- )
- );
- expect(supabaseUtils.insert).toHaveBeenNthCalledWith(
- 2,
- expect.any(Object),
- 'private_users',
- expect.objectContaining(
- {
- id: mockAuth.uid,
- }
- )
- );
- (sharedAnalytics.track as jest.Mock).mockResolvedValue(null);
- (emailHelpers.sendWelcomeEmail as jest.Mock).mockResolvedValue(null);
- (apiSetLastTimeOnline.setLastOnlineTimeUser as jest.Mock).mockResolvedValue(null);
+ const mockGetUser = jest
+ .fn()
+ .mockResolvedValueOnce(mockFirebaseUser)
+ .mockResolvedValueOnce(mockFbUser)
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp)
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket)
+ ;(usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(0)
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
+ ;(sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false)
+ ;(userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null)
+ ;(supabaseUtils.insert as jest.Mock).mockResolvedValueOnce(null).mockResolvedValueOnce(null)
+ ;(supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow)
+ ;(supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow)
- await results.continue();
+ const results: any = await createUser(mockProps, mockAuth, mockReq)
- expect(sharedAnalytics.track).toBeCalledTimes(1);
- expect(sharedAnalytics.track).toBeCalledWith(
- mockAuth.uid,
- 'create profile',
- {username: mockNewUserRow.username}
- );
- expect(emailHelpers.sendWelcomeEmail).toBeCalledTimes(1);
- expect(emailHelpers.sendWelcomeEmail).toBeCalledWith(mockNewUserRow, mockPrivateUserRow);
- expect(apiSetLastTimeOnline.setLastOnlineTimeUser).toBeCalledTimes(1);
- expect(apiSetLastTimeOnline.setLastOnlineTimeUser).toBeCalledWith(mockAuth.uid);
- });
+ expect(results.result.user).toEqual(mockNewUserRow)
+ expect(results.result.privateUser).toEqual(mockPrivateUserRow)
+ expect(mockGetUser).toBeCalledTimes(2)
+ expect(mockGetUser).toHaveBeenNthCalledWith(1, mockAuth.uid)
+ expect(mockReq.get).toBeCalledTimes(1)
+ expect(mockReq.get).toBeCalledWith(Object.keys(mockReferer.headers)[0])
+ expect(sharedAnalytics.getIp).toBeCalledTimes(1)
+ expect(sharedAnalytics.getIp).toBeCalledWith(mockReq)
+ expect(mockGetUser).toHaveBeenNthCalledWith(2, mockAuth.uid)
+ expect(usernameUtils.cleanDisplayName).toBeCalledTimes(1)
+ expect(usernameUtils.cleanDisplayName).toHaveBeenCalledWith(mockFbUser.displayName)
+ expect(usernameUtils.cleanUsername).toBeCalledTimes(1)
+ expect(usernameUtils.cleanUsername).toBeCalledWith(mockFbUser.displayName)
+ expect(mockPg.one).toBeCalledTimes(1)
+ expect(mockPg.tx).toBeCalledTimes(1)
+ expect(sharedUtils.getUser).toBeCalledTimes(1)
+ expect(sharedUtils.getUser).toHaveBeenCalledWith(mockAuth.uid, expect.any(Object))
+ expect(userNotificationPref.getDefaultNotificationPreferences).toBeCalledTimes(1)
+ expect(supabaseUtils.insert).toBeCalledTimes(2)
+ expect(supabaseUtils.insert).toHaveBeenNthCalledWith(
+ 1,
+ expect.any(Object),
+ 'users',
+ expect.objectContaining({
+ id: mockAuth.uid,
+ name: mockFbUser.displayName,
+ username: mockFbUser.displayName,
+ }),
+ )
+ expect(supabaseUtils.insert).toHaveBeenNthCalledWith(
+ 2,
+ expect.any(Object),
+ 'private_users',
+ expect.objectContaining({
+ id: mockAuth.uid,
+ }),
+ )
+ ;(sharedAnalytics.track as jest.Mock).mockResolvedValue(null)
+ ;(emailHelpers.sendWelcomeEmail as jest.Mock).mockResolvedValue(null)
+ ;(apiSetLastTimeOnline.setLastOnlineTimeUser as jest.Mock).mockResolvedValue(null)
- it('should generate a device token when creating a user', async () => {
- const mockProps = {
- deviceToken: "mockDeviceToken",
- adminToken: "mockAdminToken"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReferer = {
- headers: {
- 'referer': 'mockReferer'
- }
- };
- const mockReq = { get: jest.fn().mockReturnValue(mockReferer)} as any;
- const mockFirebaseUser = {
- providerData: [
- {
- providerId: 'password'
- }
- ],
- };
- const mockFbUser = {
- email: "mockEmail@mockServer.com",
- displayName: "mockDisplayName",
- photoURL: "mockPhotoUrl"
- };
- const mockIp = "mockIP";
- const mockBucket = {} as any;
- const mockNewUserRow = {
- created_time: "mockCreatedTime",
- data: {"mockNewUserJson": "mockNewUserJsonData"},
- id: "mockNewUserId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername"
- };
- const mockPrivateUserRow = {
- data: {"mockPrivateUserJson" : "mockPrivateUserJsonData"},
- id: "mockPrivateUserId"
- };
+ await results.continue()
- const mockGetUser = jest.fn()
- .mockResolvedValueOnce(mockFirebaseUser)
- .mockResolvedValueOnce(mockFbUser);
+ expect(sharedAnalytics.track).toBeCalledTimes(1)
+ expect(sharedAnalytics.track).toBeCalledWith(mockAuth.uid, 'create profile', {
+ username: mockNewUserRow.username,
+ })
+ expect(emailHelpers.sendWelcomeEmail).toBeCalledTimes(1)
+ expect(emailHelpers.sendWelcomeEmail).toBeCalledWith(mockNewUserRow, mockPrivateUserRow)
+ expect(apiSetLastTimeOnline.setLastOnlineTimeUser).toBeCalledTimes(1)
+ expect(apiSetLastTimeOnline.setLastOnlineTimeUser).toBeCalledWith(mockAuth.uid)
+ })
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp);
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket);
- (usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (mockPg.one as jest.Mock).mockResolvedValue(0);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
- (sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false);
- (userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null);
- (supabaseUtils.insert as jest.Mock)
- .mockResolvedValueOnce(null)
- .mockResolvedValueOnce(null);
- (supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow);
- (supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow);
-
- await createUser(mockProps, mockAuth, mockReq);
+ it('should generate a device token when creating a user', async () => {
+ const mockProps = {
+ deviceToken: 'mockDeviceToken',
+ adminToken: 'mockAdminToken',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReferer = {
+ headers: {
+ referer: 'mockReferer',
+ },
+ }
+ const mockReq = {get: jest.fn().mockReturnValue(mockReferer)} as any
+ const mockFirebaseUser = {
+ providerData: [
+ {
+ providerId: 'password',
+ },
+ ],
+ }
+ const mockFbUser = {
+ email: 'mockEmail@mockServer.com',
+ displayName: 'mockDisplayName',
+ photoURL: 'mockPhotoUrl',
+ }
+ const mockIp = 'mockIP'
+ const mockBucket = {} as any
+ const mockNewUserRow = {
+ created_time: 'mockCreatedTime',
+ data: {mockNewUserJson: 'mockNewUserJsonData'},
+ id: 'mockNewUserId',
+ name: 'mockName',
+ name_username_vector: 'mockNameUsernameVector',
+ username: 'mockUsername',
+ }
+ const mockPrivateUserRow = {
+ data: {mockPrivateUserJson: 'mockPrivateUserJsonData'},
+ id: 'mockPrivateUserId',
+ }
- expect(supabaseUtils.insert).not.toHaveBeenNthCalledWith(
- 2,
- expect.any(Object),
- 'private_users',
- {
- id: expect.any(String),
- data: expect.objectContaining(
- {
- initialDeviceToken: mockProps.deviceToken
- }
- )
- }
- );
-
- });
+ const mockGetUser = jest
+ .fn()
+ .mockResolvedValueOnce(mockFirebaseUser)
+ .mockResolvedValueOnce(mockFbUser)
- it('should generate a avatar Url when creating a user', async () => {
- const mockProps = {
- deviceToken: "mockDeviceToken",
- adminToken: "mockAdminToken"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReferer = {
- headers: {
- 'referer': 'mockReferer'
- }
- };
- const mockReq = { get: jest.fn().mockReturnValue(mockReferer)} as any;
- const mockFirebaseUser = {
- providerData: [
- {
- providerId: 'password'
- }
- ],
- };
- const mockFbUser = {
- email: "mockEmail@mockServer.com",
- displayName: "mockDisplayName",
- };
- const mockIp = "mockIP";
- const mockBucket = {} as any;
- const mockAvatarUrl = "mockGeneratedAvatarUrl"
- const mockNewUserRow = {
- created_time: "mockCreatedTime",
- data: {"mockNewUserJson": "mockNewUserJsonData"},
- id: "mockNewUserId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername"
- };
- const mockPrivateUserRow = {
- data: {"mockPrivateUserJson" : "mockPrivateUserJsonData"},
- id: "mockPrivateUserId"
- };
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp)
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket)
+ ;(usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(0)
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
+ ;(sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false)
+ ;(userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null)
+ ;(supabaseUtils.insert as jest.Mock).mockResolvedValueOnce(null).mockResolvedValueOnce(null)
+ ;(supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow)
+ ;(supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow)
- const mockGetUser = jest.fn()
- .mockResolvedValueOnce(mockFirebaseUser)
- .mockResolvedValueOnce(mockFbUser);
+ await createUser(mockProps, mockAuth, mockReq)
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp);
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket);
- (avatarHelpers.generateAvatarUrl as jest.Mock).mockResolvedValue(mockAvatarUrl);
- (usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (mockPg.one as jest.Mock).mockResolvedValue(0);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
- (sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false);
- (userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null);
- (supabaseUtils.insert as jest.Mock)
- .mockResolvedValueOnce(null)
- .mockResolvedValueOnce(null);
- (supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow);
- (supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow);
-
- await createUser(mockProps, mockAuth, mockReq);
+ expect(supabaseUtils.insert).not.toHaveBeenNthCalledWith(
+ 2,
+ expect.any(Object),
+ 'private_users',
+ {
+ id: expect.any(String),
+ data: expect.objectContaining({
+ initialDeviceToken: mockProps.deviceToken,
+ }),
+ },
+ )
+ })
- expect(objectUtils.removeUndefinedProps).toHaveBeenCalledTimes(1);
- expect(objectUtils.removeUndefinedProps).toHaveBeenCalledWith(
- {
- avatarUrl: mockAvatarUrl,
- isBannedFromPosting: false,
- link: expect.any(Object)
- }
- );
-
- });
+ it('should generate a avatar Url when creating a user', async () => {
+ const mockProps = {
+ deviceToken: 'mockDeviceToken',
+ adminToken: 'mockAdminToken',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReferer = {
+ headers: {
+ referer: 'mockReferer',
+ },
+ }
+ const mockReq = {get: jest.fn().mockReturnValue(mockReferer)} as any
+ const mockFirebaseUser = {
+ providerData: [
+ {
+ providerId: 'password',
+ },
+ ],
+ }
+ const mockFbUser = {
+ email: 'mockEmail@mockServer.com',
+ displayName: 'mockDisplayName',
+ }
+ const mockIp = 'mockIP'
+ const mockBucket = {} as any
+ const mockAvatarUrl = 'mockGeneratedAvatarUrl'
+ const mockNewUserRow = {
+ created_time: 'mockCreatedTime',
+ data: {mockNewUserJson: 'mockNewUserJsonData'},
+ id: 'mockNewUserId',
+ name: 'mockName',
+ name_username_vector: 'mockNameUsernameVector',
+ username: 'mockUsername',
+ }
+ const mockPrivateUserRow = {
+ data: {mockPrivateUserJson: 'mockPrivateUserJsonData'},
+ id: 'mockPrivateUserId',
+ }
- it('should not allow a username that already exists when creating a user', async () => {
- const mockProps = {
- deviceToken: "mockDeviceToken",
- adminToken: "mockAdminToken"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReferer = {
- headers: {
- 'referer': 'mockReferer'
- }
- };
- const mockReq = { get: jest.fn().mockReturnValue(mockReferer)} as any;
- const mockFirebaseUser = {
- providerData: [
- {
- providerId: 'passwords'
- }
- ],
- };
- const mockFbUser = {
- email: "mockEmail@mockServer.com",
- displayName: "mockDisplayName",
- photoURL: "mockPhotoUrl"
- };
- const mockIp = "mockIP";
- const mockBucket = {} as any;
- const mockNewUserRow = {
- created_time: "mockCreatedTime",
- data: {"mockNewUserJson": "mockNewUserJsonData"},
- id: "mockNewUserId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername"
- };
- const mockPrivateUserRow = {
- data: {"mockPrivateUserJson" : "mockPrivateUserJsonData"},
- id: "mockPrivateUserId"
- };
+ const mockGetUser = jest
+ .fn()
+ .mockResolvedValueOnce(mockFirebaseUser)
+ .mockResolvedValueOnce(mockFbUser)
- const mockGetUser = jest.fn()
- .mockResolvedValueOnce(mockFirebaseUser)
- .mockResolvedValueOnce(mockFbUser);
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp)
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket)
+ ;(avatarHelpers.generateAvatarUrl as jest.Mock).mockResolvedValue(mockAvatarUrl)
+ ;(usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(0)
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
+ ;(sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false)
+ ;(userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null)
+ ;(supabaseUtils.insert as jest.Mock).mockResolvedValueOnce(null).mockResolvedValueOnce(null)
+ ;(supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow)
+ ;(supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow)
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp);
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket);
- (usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (mockPg.one as jest.Mock).mockResolvedValue(1);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
- (sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false);
- (userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null);
- (supabaseUtils.insert as jest.Mock)
- .mockResolvedValueOnce(null)
- .mockResolvedValueOnce(null);
- (supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow);
- (supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow);
-
- await createUser(mockProps, mockAuth, mockReq);
+ await createUser(mockProps, mockAuth, mockReq)
- expect(mockPg.one).toBeCalledTimes(1);
- expect(supabaseUtils.insert).toBeCalledTimes(2);
- expect(supabaseUtils.insert).not.toHaveBeenNthCalledWith(
- 1,
- expect.any(Object),
- 'users',
- expect.objectContaining(
- {
- id: mockAuth.uid,
- name: mockFbUser.displayName,
- username: mockFbUser.displayName,
- }
- )
- );
- });
+ expect(objectUtils.removeUndefinedProps).toHaveBeenCalledTimes(1)
+ expect(objectUtils.removeUndefinedProps).toHaveBeenCalledWith({
+ avatarUrl: mockAvatarUrl,
+ isBannedFromPosting: false,
+ link: expect.any(Object),
+ })
+ })
- it('should successfully create a user who is banned from posting if there ip/device token is banned', async () => {
- const mockProps = {
- deviceToken: "mockDeviceToken",
- adminToken: "mockAdminToken"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReferer = {
- headers: {
- 'referer': 'mockReferer'
- }
- };
- const mockReq = { get: jest.fn().mockReturnValue(mockReferer)} as any;
- const mockFirebaseUser = {
- providerData: [
- {
- providerId: 'passwords'
- }
- ],
- };
- const mockFbUser = {
- email: "mockEmail@mockServer.com",
- displayName: "mockDisplayName",
- photoURL: "mockPhotoUrl"
- };
- const mockIp = "mockIP";
- const mockBucket = {} as any;
- const mockNewUserRow = {
- created_time: "mockCreatedTime",
- data: {"mockNewUserJson": "mockNewUserJsonData"},
- id: "mockNewUserId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername"
- };
- const mockPrivateUserRow = {
- data: {"mockPrivateUserJson" : "mockPrivateUserJsonData"},
- id: "mockPrivateUserId"
- };
+ it('should not allow a username that already exists when creating a user', async () => {
+ const mockProps = {
+ deviceToken: 'mockDeviceToken',
+ adminToken: 'mockAdminToken',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReferer = {
+ headers: {
+ referer: 'mockReferer',
+ },
+ }
+ const mockReq = {get: jest.fn().mockReturnValue(mockReferer)} as any
+ const mockFirebaseUser = {
+ providerData: [
+ {
+ providerId: 'passwords',
+ },
+ ],
+ }
+ const mockFbUser = {
+ email: 'mockEmail@mockServer.com',
+ displayName: 'mockDisplayName',
+ photoURL: 'mockPhotoUrl',
+ }
+ const mockIp = 'mockIP'
+ const mockBucket = {} as any
+ const mockNewUserRow = {
+ created_time: 'mockCreatedTime',
+ data: {mockNewUserJson: 'mockNewUserJsonData'},
+ id: 'mockNewUserId',
+ name: 'mockName',
+ name_username_vector: 'mockNameUsernameVector',
+ username: 'mockUsername',
+ }
+ const mockPrivateUserRow = {
+ data: {mockPrivateUserJson: 'mockPrivateUserJsonData'},
+ id: 'mockPrivateUserId',
+ }
- const mockGetUser = jest.fn()
- .mockResolvedValueOnce(mockFirebaseUser)
- .mockResolvedValueOnce(mockFbUser);
+ const mockGetUser = jest
+ .fn()
+ .mockResolvedValueOnce(mockFirebaseUser)
+ .mockResolvedValueOnce(mockFbUser)
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp);
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket);
- (usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (mockPg.one as jest.Mock).mockResolvedValue(0);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
- (sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false);
- jest.spyOn(Array.prototype, 'includes').mockReturnValue(true);
- (userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null);
- (supabaseUtils.insert as jest.Mock)
- .mockResolvedValueOnce(null)
- .mockResolvedValueOnce(null);
- (supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow);
- (supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow);
-
- await createUser(mockProps, mockAuth, mockReq);
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp)
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket)
+ ;(usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(1)
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
+ ;(sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false)
+ ;(userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null)
+ ;(supabaseUtils.insert as jest.Mock).mockResolvedValueOnce(null).mockResolvedValueOnce(null)
+ ;(supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow)
+ ;(supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow)
- expect(objectUtils.removeUndefinedProps).toHaveBeenCalledTimes(1);
- expect(objectUtils.removeUndefinedProps).toHaveBeenCalledWith(
- {
- avatarUrl: mockFbUser.photoURL,
- isBannedFromPosting: true,
- link: expect.any(Object)
- }
- );
- });
- });
+ await createUser(mockProps, mockAuth, mockReq)
- describe('when an error occurs', () => {
- it('should throw if the user already exists', async () => {
- const mockProps = {
- deviceToken: "mockDeviceToken",
- adminToken: "mockAdminToken"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReferer = {
- headers: {
- 'referer': 'mockReferer'
- }
- };
- const mockReq = { get: jest.fn().mockReturnValue(mockReferer)} as any;
- const mockFirebaseUser = {
- providerData: [
- {
- providerId: 'passwords'
- }
- ],
- };
- const mockFbUser = {
- email: "mockEmail@mockServer.com",
- displayName: "mockDisplayName",
- photoURL: "mockPhotoUrl"
- };
- const mockIp = "mockIP";
- const mockBucket = {} as any;
+ expect(mockPg.one).toBeCalledTimes(1)
+ expect(supabaseUtils.insert).toBeCalledTimes(2)
+ expect(supabaseUtils.insert).not.toHaveBeenNthCalledWith(
+ 1,
+ expect.any(Object),
+ 'users',
+ expect.objectContaining({
+ id: mockAuth.uid,
+ name: mockFbUser.displayName,
+ username: mockFbUser.displayName,
+ }),
+ )
+ })
- const mockGetUser = jest.fn()
- .mockResolvedValueOnce(mockFirebaseUser)
- .mockResolvedValueOnce(mockFbUser);
+ it('should successfully create a user who is banned from posting if there ip/device token is banned', async () => {
+ const mockProps = {
+ deviceToken: 'mockDeviceToken',
+ adminToken: 'mockAdminToken',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReferer = {
+ headers: {
+ referer: 'mockReferer',
+ },
+ }
+ const mockReq = {get: jest.fn().mockReturnValue(mockReferer)} as any
+ const mockFirebaseUser = {
+ providerData: [
+ {
+ providerId: 'passwords',
+ },
+ ],
+ }
+ const mockFbUser = {
+ email: 'mockEmail@mockServer.com',
+ displayName: 'mockDisplayName',
+ photoURL: 'mockPhotoUrl',
+ }
+ const mockIp = 'mockIP'
+ const mockBucket = {} as any
+ const mockNewUserRow = {
+ created_time: 'mockCreatedTime',
+ data: {mockNewUserJson: 'mockNewUserJsonData'},
+ id: 'mockNewUserId',
+ name: 'mockName',
+ name_username_vector: 'mockNameUsernameVector',
+ username: 'mockUsername',
+ }
+ const mockPrivateUserRow = {
+ data: {mockPrivateUserJson: 'mockPrivateUserJsonData'},
+ id: 'mockPrivateUserId',
+ }
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp);
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket);
- (usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (mockPg.one as jest.Mock).mockResolvedValue(0);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(true);
-
- expect(createUser(mockProps, mockAuth, mockReq))
- .rejects
- .toThrowError('User already exists');
- });
+ const mockGetUser = jest
+ .fn()
+ .mockResolvedValueOnce(mockFirebaseUser)
+ .mockResolvedValueOnce(mockFbUser)
- it('should throw if the username is already taken', async () => {
- const mockProps = {
- deviceToken: "mockDeviceToken",
- adminToken: "mockAdminToken"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReferer = {
- headers: {
- 'referer': 'mockReferer'
- }
- };
- const mockReq = { get: jest.fn().mockReturnValue(mockReferer)} as any;
- const mockFirebaseUser = {
- providerData: [
- {
- providerId: 'passwords'
- }
- ],
- };
- const mockFbUser = {
- email: "mockEmail@mockServer.com",
- displayName: "mockDisplayName",
- photoURL: "mockPhotoUrl"
- };
- const mockIp = "mockIP";
- const mockBucket = {} as any;
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp)
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket)
+ ;(usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(0)
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
+ ;(sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false)
+ jest.spyOn(Array.prototype, 'includes').mockReturnValue(true)
+ ;(userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null)
+ ;(supabaseUtils.insert as jest.Mock).mockResolvedValueOnce(null).mockResolvedValueOnce(null)
+ ;(supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow)
+ ;(supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow)
- const mockGetUser = jest.fn()
- .mockResolvedValueOnce(mockFirebaseUser)
- .mockResolvedValueOnce(mockFbUser);
+ await createUser(mockProps, mockAuth, mockReq)
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp);
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket);
- (usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (mockPg.one as jest.Mock).mockResolvedValue(0);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
- (sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(true);
-
- expect(createUser(mockProps, mockAuth, mockReq))
- .rejects
- .toThrowError('Username already taken');
- });
+ expect(objectUtils.removeUndefinedProps).toHaveBeenCalledTimes(1)
+ expect(objectUtils.removeUndefinedProps).toHaveBeenCalledWith({
+ avatarUrl: mockFbUser.photoURL,
+ isBannedFromPosting: true,
+ link: expect.any(Object),
+ })
+ })
+ })
- it('should throw if failed to track create profile', async () => {
- const mockProps = {
- deviceToken: "mockDeviceToken",
- adminToken: "mockAdminToken"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReferer = {
- headers: {
- 'referer': 'mockReferer'
- }
- };
- const mockReq = { get: jest.fn().mockReturnValue(mockReferer)} as any;
- const mockFirebaseUser = {
- providerData: [
- {
- providerId: 'passwords'
- }
- ],
- };
- const mockFbUser = {
- email: "mockEmail@mockServer.com",
- displayName: "mockDisplayName",
- photoURL: "mockPhotoUrl"
- };
- const mockIp = "mockIP";
- const mockBucket = {} as any;
- const mockNewUserRow = {
- created_time: "mockCreatedTime",
- data: {"mockNewUserJson": "mockNewUserJsonData"},
- id: "mockNewUserId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername"
- };
- const mockPrivateUserRow = {
- data: {"mockPrivateUserJson" : "mockPrivateUserJsonData"},
- id: "mockPrivateUserId"
- };
+ describe('when an error occurs', () => {
+ it('should throw if the user already exists', async () => {
+ const mockProps = {
+ deviceToken: 'mockDeviceToken',
+ adminToken: 'mockAdminToken',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReferer = {
+ headers: {
+ referer: 'mockReferer',
+ },
+ }
+ const mockReq = {get: jest.fn().mockReturnValue(mockReferer)} as any
+ const mockFirebaseUser = {
+ providerData: [
+ {
+ providerId: 'passwords',
+ },
+ ],
+ }
+ const mockFbUser = {
+ email: 'mockEmail@mockServer.com',
+ displayName: 'mockDisplayName',
+ photoURL: 'mockPhotoUrl',
+ }
+ const mockIp = 'mockIP'
+ const mockBucket = {} as any
- const mockGetUser = jest.fn()
- .mockResolvedValueOnce(mockFirebaseUser)
- .mockResolvedValueOnce(mockFbUser);
+ const mockGetUser = jest
+ .fn()
+ .mockResolvedValueOnce(mockFirebaseUser)
+ .mockResolvedValueOnce(mockFbUser)
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp);
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket);
- (usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (mockPg.one as jest.Mock).mockResolvedValue(0);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
- (sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false);
- (userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null);
- (supabaseUtils.insert as jest.Mock)
- .mockResolvedValueOnce(null)
- .mockResolvedValueOnce(null);
- (supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow);
- (supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow);
-
- const results: any = await createUser(mockProps, mockAuth, mockReq);
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp)
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket)
+ ;(usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(0)
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(true)
- (sharedAnalytics.track as jest.Mock).mockRejectedValue(new Error('Tracking failed'));
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ expect(createUser(mockProps, mockAuth, mockReq)).rejects.toThrowError('User already exists')
+ })
- await results.continue();
+ it('should throw if the username is already taken', async () => {
+ const mockProps = {
+ deviceToken: 'mockDeviceToken',
+ adminToken: 'mockAdminToken',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReferer = {
+ headers: {
+ referer: 'mockReferer',
+ },
+ }
+ const mockReq = {get: jest.fn().mockReturnValue(mockReferer)} as any
+ const mockFirebaseUser = {
+ providerData: [
+ {
+ providerId: 'passwords',
+ },
+ ],
+ }
+ const mockFbUser = {
+ email: 'mockEmail@mockServer.com',
+ displayName: 'mockDisplayName',
+ photoURL: 'mockPhotoUrl',
+ }
+ const mockIp = 'mockIP'
+ const mockBucket = {} as any
- expect(errorSpy).toHaveBeenCalledWith('Failed to track create profile', expect.any(Error));
- });
+ const mockGetUser = jest
+ .fn()
+ .mockResolvedValueOnce(mockFirebaseUser)
+ .mockResolvedValueOnce(mockFbUser)
- it('should throw if failed to send a welcome email', async () => {
- Object.defineProperty(hostingConstants, 'IS_LOCAL', {
- value: false,
- writable: true
- });
- const mockProps = {
- deviceToken: "mockDeviceToken",
- adminToken: "mockAdminToken"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReferer = {
- headers: {
- 'referer': 'mockReferer'
- }
- };
- const mockReq = { get: jest.fn().mockReturnValue(mockReferer)} as any;
- const mockFirebaseUser = {
- providerData: [
- {
- providerId: 'passwords'
- }
- ],
- };
- const mockFbUser = {
- email: "mockEmail@mockServer.com",
- displayName: "mockDisplayName",
- photoURL: "mockPhotoUrl"
- };
- const mockIp = "mockIP";
- const mockBucket = {} as any;
- const mockNewUserRow = {
- created_time: "mockCreatedTime",
- data: {"mockNewUserJson": "mockNewUserJsonData"},
- id: "mockNewUserId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername"
- };
- const mockPrivateUserRow = {
- data: {"mockPrivateUserJson" : "mockPrivateUserJsonData"},
- id: "mockPrivateUserId"
- };
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp)
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket)
+ ;(usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(0)
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
+ ;(sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(true)
- const mockGetUser = jest.fn()
- .mockResolvedValueOnce(mockFirebaseUser)
- .mockResolvedValueOnce(mockFbUser);
+ expect(createUser(mockProps, mockAuth, mockReq)).rejects.toThrowError(
+ 'Username already taken',
+ )
+ })
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp);
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket);
- (usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (mockPg.one as jest.Mock).mockResolvedValue(0);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
- (sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false);
- (userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null);
- (supabaseUtils.insert as jest.Mock)
- .mockResolvedValueOnce(null)
- .mockResolvedValueOnce(null);
- (supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow);
- (supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow);
-
- const results: any = await createUser(mockProps, mockAuth, mockReq);
+ it('should throw if failed to track create profile', async () => {
+ const mockProps = {
+ deviceToken: 'mockDeviceToken',
+ adminToken: 'mockAdminToken',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReferer = {
+ headers: {
+ referer: 'mockReferer',
+ },
+ }
+ const mockReq = {get: jest.fn().mockReturnValue(mockReferer)} as any
+ const mockFirebaseUser = {
+ providerData: [
+ {
+ providerId: 'passwords',
+ },
+ ],
+ }
+ const mockFbUser = {
+ email: 'mockEmail@mockServer.com',
+ displayName: 'mockDisplayName',
+ photoURL: 'mockPhotoUrl',
+ }
+ const mockIp = 'mockIP'
+ const mockBucket = {} as any
+ const mockNewUserRow = {
+ created_time: 'mockCreatedTime',
+ data: {mockNewUserJson: 'mockNewUserJsonData'},
+ id: 'mockNewUserId',
+ name: 'mockName',
+ name_username_vector: 'mockNameUsernameVector',
+ username: 'mockUsername',
+ }
+ const mockPrivateUserRow = {
+ data: {mockPrivateUserJson: 'mockPrivateUserJsonData'},
+ id: 'mockPrivateUserId',
+ }
- (sharedAnalytics.track as jest.Mock).mockResolvedValue(null);
- (emailHelpers.sendWelcomeEmail as jest.Mock).mockRejectedValue(new Error('Welcome email failed'));
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ const mockGetUser = jest
+ .fn()
+ .mockResolvedValueOnce(mockFirebaseUser)
+ .mockResolvedValueOnce(mockFbUser)
- await results.continue();
-
- expect(errorSpy).toBeCalledWith('Failed to sendWelcomeEmail', expect.any(Error));
- });
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp)
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket)
+ ;(usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(0)
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
+ ;(sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false)
+ ;(userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null)
+ ;(supabaseUtils.insert as jest.Mock).mockResolvedValueOnce(null).mockResolvedValueOnce(null)
+ ;(supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow)
+ ;(supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow)
- it('should throw if failed to set last time online', async () => {
- const mockProps = {
- deviceToken: "mockDeviceToken",
- adminToken: "mockAdminToken"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReferer = {
- headers: {
- 'referer': 'mockReferer'
- }
- };
- const mockReq = { get: jest.fn().mockReturnValue(mockReferer)} as any;
- const mockFirebaseUser = {
- providerData: [
- {
- providerId: 'passwords'
- }
- ],
- };
- const mockFbUser = {
- email: "mockEmail@mockServer.com",
- displayName: "mockDisplayName",
- photoURL: "mockPhotoUrl"
- };
- const mockIp = "mockIP";
- const mockBucket = {} as any;
- const mockNewUserRow = {
- created_time: "mockCreatedTime",
- data: {"mockNewUserJson": "mockNewUserJsonData"},
- id: "mockNewUserId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername"
- };
- const mockPrivateUserRow = {
- data: {"mockPrivateUserJson" : "mockPrivateUserJsonData"},
- id: "mockPrivateUserId"
- };
+ const results: any = await createUser(mockProps, mockAuth, mockReq)
- const mockGetUser = jest.fn()
- .mockResolvedValueOnce(mockFirebaseUser)
- .mockResolvedValueOnce(mockFbUser);
+ ;(sharedAnalytics.track as jest.Mock).mockRejectedValue(new Error('Tracking failed'))
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp);
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- getUser: mockGetUser
- });
- (usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket);
- (usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName);
- (mockPg.one as jest.Mock).mockResolvedValue(0);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
- (sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false);
- (userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null);
- (supabaseUtils.insert as jest.Mock)
- .mockResolvedValueOnce(null)
- .mockResolvedValueOnce(null);
- (supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow);
- (supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow);
-
- const results: any = await createUser(mockProps, mockAuth, mockReq);
+ await results.continue()
- (sharedAnalytics.track as jest.Mock).mockResolvedValue(null);
- (emailHelpers.sendWelcomeEmail as jest.Mock).mockResolvedValue(null);
- (apiSetLastTimeOnline.setLastOnlineTimeUser as jest.Mock).mockRejectedValue(new Error('Failed to set last online time'));
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ expect(errorSpy).toHaveBeenCalledWith('Failed to track create profile', expect.any(Error))
+ })
- await results.continue();
-
- expect(errorSpy).toHaveBeenCalledWith('Failed to set last online time', expect.any(Error));
- });
- });
-});
\ No newline at end of file
+ it('should throw if failed to send a welcome email', async () => {
+ Object.defineProperty(hostingConstants, 'IS_LOCAL', {
+ value: false,
+ writable: true,
+ })
+ const mockProps = {
+ deviceToken: 'mockDeviceToken',
+ adminToken: 'mockAdminToken',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReferer = {
+ headers: {
+ referer: 'mockReferer',
+ },
+ }
+ const mockReq = {get: jest.fn().mockReturnValue(mockReferer)} as any
+ const mockFirebaseUser = {
+ providerData: [
+ {
+ providerId: 'passwords',
+ },
+ ],
+ }
+ const mockFbUser = {
+ email: 'mockEmail@mockServer.com',
+ displayName: 'mockDisplayName',
+ photoURL: 'mockPhotoUrl',
+ }
+ const mockIp = 'mockIP'
+ const mockBucket = {} as any
+ const mockNewUserRow = {
+ created_time: 'mockCreatedTime',
+ data: {mockNewUserJson: 'mockNewUserJsonData'},
+ id: 'mockNewUserId',
+ name: 'mockName',
+ name_username_vector: 'mockNameUsernameVector',
+ username: 'mockUsername',
+ }
+ const mockPrivateUserRow = {
+ data: {mockPrivateUserJson: 'mockPrivateUserJsonData'},
+ id: 'mockPrivateUserId',
+ }
+
+ const mockGetUser = jest
+ .fn()
+ .mockResolvedValueOnce(mockFirebaseUser)
+ .mockResolvedValueOnce(mockFbUser)
+
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp)
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket)
+ ;(usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(0)
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
+ ;(sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false)
+ ;(userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null)
+ ;(supabaseUtils.insert as jest.Mock).mockResolvedValueOnce(null).mockResolvedValueOnce(null)
+ ;(supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow)
+ ;(supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow)
+
+ const results: any = await createUser(mockProps, mockAuth, mockReq)
+
+ ;(sharedAnalytics.track as jest.Mock).mockResolvedValue(null)
+ ;(emailHelpers.sendWelcomeEmail as jest.Mock).mockRejectedValue(
+ new Error('Welcome email failed'),
+ )
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
+
+ await results.continue()
+
+ expect(errorSpy).toBeCalledWith('Failed to sendWelcomeEmail', expect.any(Error))
+ })
+
+ it('should throw if failed to set last time online', async () => {
+ const mockProps = {
+ deviceToken: 'mockDeviceToken',
+ adminToken: 'mockAdminToken',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReferer = {
+ headers: {
+ referer: 'mockReferer',
+ },
+ }
+ const mockReq = {get: jest.fn().mockReturnValue(mockReferer)} as any
+ const mockFirebaseUser = {
+ providerData: [
+ {
+ providerId: 'passwords',
+ },
+ ],
+ }
+ const mockFbUser = {
+ email: 'mockEmail@mockServer.com',
+ displayName: 'mockDisplayName',
+ photoURL: 'mockPhotoUrl',
+ }
+ const mockIp = 'mockIP'
+ const mockBucket = {} as any
+ const mockNewUserRow = {
+ created_time: 'mockCreatedTime',
+ data: {mockNewUserJson: 'mockNewUserJsonData'},
+ id: 'mockNewUserId',
+ name: 'mockName',
+ name_username_vector: 'mockNameUsernameVector',
+ username: 'mockUsername',
+ }
+ const mockPrivateUserRow = {
+ data: {mockPrivateUserJson: 'mockPrivateUserJsonData'},
+ id: 'mockPrivateUserId',
+ }
+
+ const mockGetUser = jest
+ .fn()
+ .mockResolvedValueOnce(mockFirebaseUser)
+ .mockResolvedValueOnce(mockFbUser)
+
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(sharedAnalytics.getIp as jest.Mock).mockReturnValue(mockIp)
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ getUser: mockGetUser,
+ })
+ ;(usernameUtils.cleanDisplayName as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(firebaseUtils.getBucket as jest.Mock).mockReturnValue(mockBucket)
+ ;(usernameUtils.cleanUsername as jest.Mock).mockReturnValue(mockFbUser.displayName)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(0)
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
+ ;(sharedUtils.getUserByUsername as jest.Mock).mockResolvedValue(false)
+ ;(userNotificationPref.getDefaultNotificationPreferences as jest.Mock).mockReturnValue(null)
+ ;(supabaseUtils.insert as jest.Mock).mockResolvedValueOnce(null).mockResolvedValueOnce(null)
+ ;(supabaseUsers.convertUser as jest.Mock).mockReturnValue(mockNewUserRow)
+ ;(supabaseUsers.convertPrivateUser as jest.Mock).mockReturnValue(mockPrivateUserRow)
+
+ const results: any = await createUser(mockProps, mockAuth, mockReq)
+
+ ;(sharedAnalytics.track as jest.Mock).mockResolvedValue(null)
+ ;(emailHelpers.sendWelcomeEmail as jest.Mock).mockResolvedValue(null)
+ ;(apiSetLastTimeOnline.setLastOnlineTimeUser as jest.Mock).mockRejectedValue(
+ new Error('Failed to set last online time'),
+ )
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
+
+ await results.continue()
+
+ expect(errorSpy).toHaveBeenCalledWith('Failed to set last online time', expect.any(Error))
+ })
+ })
+})
diff --git a/backend/api/tests/unit/create-vote.unit.test.ts b/backend/api/tests/unit/create-vote.unit.test.ts
index 3b87da8e..fecf6047 100644
--- a/backend/api/tests/unit/create-vote.unit.test.ts
+++ b/backend/api/tests/unit/create-vote.unit.test.ts
@@ -1,98 +1,89 @@
-jest.mock('shared/supabase/init');
-jest.mock('shared/utils');
-jest.mock('shared/supabase/utils');
-jest.mock('common/util/try-catch');
+jest.mock('shared/supabase/init')
+jest.mock('shared/utils')
+jest.mock('shared/supabase/utils')
+jest.mock('common/util/try-catch')
-import { createVote } from "api/create-vote";
-import * as supabaseInit from "shared/supabase/init";
-import * as sharedUtils from "shared/utils";
-import * as supabaseUtils from "shared/supabase/utils";
-import { tryCatch } from "common/util/try-catch";
-import { AuthedUser } from "api/helpers/endpoint";
+import {createVote} from 'api/create-vote'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {tryCatch} from 'common/util/try-catch'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseUtils from 'shared/supabase/utils'
+import * as sharedUtils from 'shared/utils'
describe('createVote', () => {
- beforeEach(() => {
- jest.resetAllMocks();
- const mockPg = {} as any;
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg)
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ beforeEach(() => {
+ jest.resetAllMocks()
+ const mockPg = {} as any
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should successfully creates a vote', async () => {
- const mockProps = {
- title: 'mockTitle',
- description: {'mockDescription': 'mockDescriptionValue'},
- isAnonymous: true
- };
- const mockCreator = {id: '123'};
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockData = {
- creator_id: mockCreator.id,
- title: 'mockTitle',
- description: {'mockDescription': 'mockDescriptionValue'},
- is_anonymous: true,
- status: 'voting_open'
- };
+ describe('when given valid input', () => {
+ it('should successfully creates a vote', async () => {
+ const mockProps = {
+ title: 'mockTitle',
+ description: {mockDescription: 'mockDescriptionValue'},
+ isAnonymous: true,
+ }
+ const mockCreator = {id: '123'}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockData = {
+ creator_id: mockCreator.id,
+ title: 'mockTitle',
+ description: {mockDescription: 'mockDescriptionValue'},
+ is_anonymous: true,
+ status: 'voting_open',
+ }
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator);
- (tryCatch as jest.Mock).mockResolvedValue({data: mockData , error: null});
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockData, error: null})
- const result = await createVote(mockProps, mockAuth, mockReq);
- expect(result.data).toEqual(mockData);
- expect(sharedUtils.getUser).toBeCalledTimes(1);
- expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid);
- expect(supabaseUtils.insert).toBeCalledTimes(1);
- expect(supabaseUtils.insert).toHaveBeenCalledWith(
- expect.any(Object),
- 'votes',
- {
- creator_id: mockCreator.id,
- title: mockProps.title,
- description: mockProps.description,
- is_anonymous: mockProps.isAnonymous,
- status: 'voting_open'
- }
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if the account was not found', async () => {
- const mockProps = {
- title: 'mockTitle',
- description: {'mockDescription': 'mockDescriptionValue'},
- isAnonymous: true
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ const result = await createVote(mockProps, mockAuth, mockReq)
+ expect(result.data).toEqual(mockData)
+ expect(sharedUtils.getUser).toBeCalledTimes(1)
+ expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid)
+ expect(supabaseUtils.insert).toBeCalledTimes(1)
+ expect(supabaseUtils.insert).toHaveBeenCalledWith(expect.any(Object), 'votes', {
+ creator_id: mockCreator.id,
+ title: mockProps.title,
+ description: mockProps.description,
+ is_anonymous: mockProps.isAnonymous,
+ status: 'voting_open',
+ })
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if the account was not found', async () => {
+ const mockProps = {
+ title: 'mockTitle',
+ description: {mockDescription: 'mockDescriptionValue'},
+ isAnonymous: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(null);
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(null)
- expect(createVote(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Your account was not found');
- });
+ expect(createVote(mockProps, mockAuth, mockReq)).rejects.toThrow('Your account was not found')
+ })
- it('should throw if unable to create a question', async () => {
- const mockProps = {
- title: 'mockTitle',
- description: {'mockDescription': 'mockDescriptionValue'},
- isAnonymous: true
- };
- const mockCreator = {id: '123'};
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ it('should throw if unable to create a question', async () => {
+ const mockProps = {
+ title: 'mockTitle',
+ description: {mockDescription: 'mockDescriptionValue'},
+ isAnonymous: true,
+ }
+ const mockCreator = {id: '123'}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator);
- (tryCatch as jest.Mock).mockResolvedValue({data: null , error: Error});
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockCreator)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error})
- expect(createVote(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Error creating question');
- });
- });
-});
\ No newline at end of file
+ expect(createVote(mockProps, mockAuth, mockReq)).rejects.toThrow('Error creating question')
+ })
+ })
+})
diff --git a/backend/api/tests/unit/delete-bookmarked-search.unit.test.ts b/backend/api/tests/unit/delete-bookmarked-search.unit.test.ts
index 09fee099..811014cb 100644
--- a/backend/api/tests/unit/delete-bookmarked-search.unit.test.ts
+++ b/backend/api/tests/unit/delete-bookmarked-search.unit.test.ts
@@ -1,44 +1,40 @@
-import {sqlMatch} from "common/test-utils";
-import {deleteBookmarkedSearch} from "api/delete-bookmarked-search";
-import {AuthedUser} from "api/helpers/endpoint";
-import * as supabaseInit from "shared/supabase/init";
+import {deleteBookmarkedSearch} from 'api/delete-bookmarked-search'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
-jest.mock('shared/supabase/init');
+jest.mock('shared/supabase/init')
describe('deleteBookmarkedSearch', () => {
- let mockPg = {} as any;
+ let mockPg = {} as any
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- none: jest.fn(),
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should successfully deletes a bookmarked search', async () => {
- const mockProps = {
- id: 123
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ describe('when given valid input', () => {
+ it('should successfully deletes a bookmarked search', async () => {
+ const mockProps = {
+ id: 123,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- const result = await deleteBookmarkedSearch(mockProps, mockAuth, mockReq);
-
- expect(result).toStrictEqual({});
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('DELETE FROM bookmarked_searches'),
- [
- mockProps.id,
- mockAuth.uid
- ]
- );
- });
- });
-});
\ No newline at end of file
+ const result = await deleteBookmarkedSearch(mockProps, mockAuth, mockReq)
+
+ expect(result).toStrictEqual({})
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(sqlMatch('DELETE FROM bookmarked_searches'), [
+ mockProps.id,
+ mockAuth.uid,
+ ])
+ })
+ })
+})
diff --git a/backend/api/tests/unit/delete-compatibility-answers.unit.test.ts b/backend/api/tests/unit/delete-compatibility-answers.unit.test.ts
index 731a3239..32c9fcfb 100644
--- a/backend/api/tests/unit/delete-compatibility-answers.unit.test.ts
+++ b/backend/api/tests/unit/delete-compatibility-answers.unit.test.ts
@@ -1,72 +1,64 @@
-jest.mock('shared/supabase/init');
-jest.mock('shared/compatibility/compute-scores');
+jest.mock('shared/supabase/init')
+jest.mock('shared/compatibility/compute-scores')
-import {sqlMatch} from "common/test-utils";
-import {deleteCompatibilityAnswer} from "api/delete-compatibility-answer";
-import * as supabaseInit from "shared/supabase/init";
-import {recomputeCompatibilityScoresForUser} from "shared/compatibility/compute-scores";
-import {AuthedUser} from "api/helpers/endpoint";
+import {deleteCompatibilityAnswer} from 'api/delete-compatibility-answer'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {sqlMatch} from 'common/test-utils'
+import {recomputeCompatibilityScoresForUser} from 'shared/compatibility/compute-scores'
+import * as supabaseInit from 'shared/supabase/init'
describe('deleteCompatibilityAnswers', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- none: jest.fn()
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should successfully delete compatibility answers', async () => {
- const mockProps = {
- id: 123
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ describe('when given valid input', () => {
+ it('should successfully delete compatibility answers', async () => {
+ const mockProps = {
+ id: 123,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(true);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(true)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
- const results: any = await deleteCompatibilityAnswer(mockProps, mockAuth, mockReq);
+ const results: any = await deleteCompatibilityAnswer(mockProps, mockAuth, mockReq)
- expect(results.status).toBe('success');
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch(`SELECT *`),
- [mockProps.id, mockAuth.uid]
- );
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('DELETE'),
- [mockProps.id, mockAuth.uid]
- );
+ expect(results.status).toBe('success')
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(sqlMatch(`SELECT *`), [mockProps.id, mockAuth.uid])
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(sqlMatch('DELETE'), [mockProps.id, mockAuth.uid])
- await results.continue();
-
- (recomputeCompatibilityScoresForUser as jest.Mock).mockResolvedValue(null);
- expect(recomputeCompatibilityScoresForUser).toBeCalledTimes(1);
- expect(recomputeCompatibilityScoresForUser).toBeCalledWith(mockAuth.uid, expect.any(Object));
- });
- });
- describe('when an error occurs', () => {
- it('should throw if the user is not the answers author', async () => {
- const mockProps = {
- id: 123
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ await results.continue()
+ ;(recomputeCompatibilityScoresForUser as jest.Mock).mockResolvedValue(null)
+ expect(recomputeCompatibilityScoresForUser).toBeCalledTimes(1)
+ expect(recomputeCompatibilityScoresForUser).toBeCalledWith(mockAuth.uid, expect.any(Object))
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if the user is not the answers author', async () => {
+ const mockProps = {
+ id: 123,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(false);
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(false)
- expect(deleteCompatibilityAnswer(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Item not found');
- });
- });
-});
\ No newline at end of file
+ expect(deleteCompatibilityAnswer(mockProps, mockAuth, mockReq)).rejects.toThrow(
+ 'Item not found',
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/delete-me.unit.test.ts b/backend/api/tests/unit/delete-me.unit.test.ts
index b7847fba..5fa70142 100644
--- a/backend/api/tests/unit/delete-me.unit.test.ts
+++ b/backend/api/tests/unit/delete-me.unit.test.ts
@@ -1,122 +1,112 @@
-import {sqlMatch} from "common/test-utils";
-import {deleteMe} from "api/delete-me";
-import * as supabaseInit from "shared/supabase/init";
-import * as sharedUtils from "shared/utils";
-import * as firebaseAdmin from "firebase-admin";
-import * as firebaseUtils from "shared/firebase-utils";
-import {AuthedUser} from "api/helpers/endpoint";
+import {deleteMe} from 'api/delete-me'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {sqlMatch} from 'common/test-utils'
+import * as firebaseAdmin from 'firebase-admin'
+import * as firebaseUtils from 'shared/firebase-utils'
+import * as supabaseInit from 'shared/supabase/init'
+import * as sharedUtils from 'shared/utils'
-jest.mock('shared/supabase/init');
-jest.mock('shared/utils');
+jest.mock('shared/supabase/init')
+jest.mock('shared/utils')
jest.mock('firebase-admin', () => ({
- auth: jest.fn()
-}));
-jest.mock('shared/firebase-utils');
+ auth: jest.fn(),
+}))
+jest.mock('shared/firebase-utils')
describe('deleteMe', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- none: jest.fn()
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg)
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should delete the user account from supabase and firebase', async () => {
- const mockUser = {
- id: "mockId",
- username: "mockUsername"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockRef = {} as any;
+ describe('when given valid input', () => {
+ it('should delete the user account from supabase and firebase', async () => {
+ const mockUser = {
+ id: 'mockId',
+ username: 'mockUsername',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockRef = {} as any
- const mockDeleteUser = jest.fn().mockResolvedValue(null);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (firebaseUtils.deleteUserFiles as jest.Mock).mockResolvedValue(null);
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- deleteUser: mockDeleteUser
- });
- const debugSpy = jest.spyOn(console, 'debug').mockImplementation(() => {});
+ const mockDeleteUser = jest.fn().mockResolvedValue(null)
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(firebaseUtils.deleteUserFiles as jest.Mock).mockResolvedValue(null)
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ deleteUser: mockDeleteUser,
+ })
+ const debugSpy = jest.spyOn(console, 'debug').mockImplementation(() => {})
- await deleteMe(mockRef, mockAuth, mockRef);
-
- expect(sharedUtils.getUser).toBeCalledTimes(1);
- expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid);
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('DELETE FROM users WHERE id = $1'),
- [mockUser.id]
- );
- expect(firebaseUtils.deleteUserFiles).toBeCalledTimes(1);
- expect(firebaseUtils.deleteUserFiles).toBeCalledWith(mockUser.username);
- expect(mockDeleteUser).toBeCalledTimes(1);
- expect(mockDeleteUser).toBeCalledWith(mockUser.id);
+ await deleteMe(mockRef, mockAuth, mockRef)
- expect(debugSpy).toBeCalledWith(
- expect.stringContaining(mockUser.id)
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if the user account was not found', async () => {
- const mockUser = {
- id: "mockId",
- username: "mockUsername"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockRef = {} as any;
+ expect(sharedUtils.getUser).toBeCalledTimes(1)
+ expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid)
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(sqlMatch('DELETE FROM users WHERE id = $1'), [mockUser.id])
+ expect(firebaseUtils.deleteUserFiles).toBeCalledTimes(1)
+ expect(firebaseUtils.deleteUserFiles).toBeCalledWith(mockUser.username)
+ expect(mockDeleteUser).toBeCalledTimes(1)
+ expect(mockDeleteUser).toBeCalledWith(mockUser.id)
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(null);
+ expect(debugSpy).toBeCalledWith(expect.stringContaining(mockUser.id))
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if the user account was not found', async () => {
+ const _mockUser = {
+ id: 'mockId',
+ username: 'mockUsername',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockRef = {} as any
- expect(deleteMe(mockRef, mockAuth, mockRef))
- .rejects
- .toThrow('Your account was not found');
- });
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(null)
- it('should throw an error if there is no userId', async () => {
- const mockUser = {
- username: "mockUsername"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockRef = {} as any;
+ expect(deleteMe(mockRef, mockAuth, mockRef)).rejects.toThrow('Your account was not found')
+ })
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
+ it('should throw an error if there is no userId', async () => {
+ const mockUser = {
+ username: 'mockUsername',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockRef = {} as any
- expect(deleteMe(mockRef, mockAuth, mockRef))
- .rejects
- .toThrow('Invalid user ID');
- });
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
- it('should throw if unable to remove user from firebase auth', async () => {
- const mockUser = {
- id: "mockId",
- username: "mockUsername"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockRef = {} as any;
+ expect(deleteMe(mockRef, mockAuth, mockRef)).rejects.toThrow('Invalid user ID')
+ })
- const mockDeleteUser = jest.fn().mockRejectedValue(new Error('Error during deletion'));
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (firebaseUtils.deleteUserFiles as jest.Mock).mockResolvedValue(null);
- (firebaseAdmin.auth as jest.Mock).mockReturnValue({
- deleteUser: mockDeleteUser
- });
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ it('should throw if unable to remove user from firebase auth', async () => {
+ const mockUser = {
+ id: 'mockId',
+ username: 'mockUsername',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockRef = {} as any
- await deleteMe(mockRef, mockAuth, mockRef);
+ const mockDeleteUser = jest.fn().mockRejectedValue(new Error('Error during deletion'))
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(firebaseUtils.deleteUserFiles as jest.Mock).mockResolvedValue(null)
+ ;(firebaseAdmin.auth as jest.Mock).mockReturnValue({
+ deleteUser: mockDeleteUser,
+ })
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
- expect(errorSpy).toBeCalledWith(
- expect.stringContaining('Error deleting user from Firebase Auth:'),
- expect.any(Error)
- );
- });
- });
-});
\ No newline at end of file
+ await deleteMe(mockRef, mockAuth, mockRef)
+
+ expect(errorSpy).toBeCalledWith(
+ expect.stringContaining('Error deleting user from Firebase Auth:'),
+ expect.any(Error),
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/delete-message.unit.test.ts b/backend/api/tests/unit/delete-message.unit.test.ts
index 7c16f4ca..fe11ae3f 100644
--- a/backend/api/tests/unit/delete-message.unit.test.ts
+++ b/backend/api/tests/unit/delete-message.unit.test.ts
@@ -1,101 +1,99 @@
-jest.mock('shared/supabase/init');
-jest.mock('api/helpers/private-messages');
+jest.mock('shared/supabase/init')
+jest.mock('api/helpers/private-messages')
-import {sqlMatch} from "common/test-utils";
-import {deleteMessage} from "api/delete-message";
-import * as supabaseInit from "shared/supabase/init";
-import * as messageHelpers from "api/helpers/private-messages";
-import {AuthedUser} from "api/helpers/endpoint";
+import {deleteMessage} from 'api/delete-message'
+import {AuthedUser} from 'api/helpers/endpoint'
+import * as messageHelpers from 'api/helpers/private-messages'
+import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
describe('deleteMessage', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- none: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- describe('when given valid input', () => {
- it('should delete a message', async () => {
- const mockMessageId = {
- messageId: 123
- };
- const mockMessage = {
- channel_id: "mockChannelId"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ describe('when given valid input', () => {
+ it('should delete a message', async () => {
+ const mockMessageId = {
+ messageId: 123,
+ }
+ const mockMessage = {
+ channel_id: 'mockChannelId',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (messageHelpers.broadcastPrivateMessages as jest.Mock).mockResolvedValue(null);
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(messageHelpers.broadcastPrivateMessages as jest.Mock).mockResolvedValue(null)
- const results = await deleteMessage(mockMessageId, mockAuth, mockReq);
- expect(results.success).toBeTruthy();
+ const results = await deleteMessage(mockMessageId, mockAuth, mockReq)
+ expect(results.success).toBeTruthy()
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('SELECT *'),
- [mockMessageId.messageId, mockAuth.uid]
- );
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('DELETE'),
- [mockMessageId.messageId, mockAuth.uid]
- );
- expect(messageHelpers.broadcastPrivateMessages).toBeCalledTimes(1);
- expect(messageHelpers.broadcastPrivateMessages).toBeCalledWith(
- expect.any(Object),
- mockMessage.channel_id,
- mockAuth.uid
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if the message was not found', async () => {
- const mockMessageId = {
- messageId: 123
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(sqlMatch('SELECT *'), [
+ mockMessageId.messageId,
+ mockAuth.uid,
+ ])
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(sqlMatch('DELETE'), [
+ mockMessageId.messageId,
+ mockAuth.uid,
+ ])
+ expect(messageHelpers.broadcastPrivateMessages).toBeCalledTimes(1)
+ expect(messageHelpers.broadcastPrivateMessages).toBeCalledWith(
+ expect.any(Object),
+ mockMessage.channel_id,
+ mockAuth.uid,
+ )
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if the message was not found', async () => {
+ const mockMessageId = {
+ messageId: 123,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
- expect(deleteMessage(mockMessageId, mockAuth, mockReq))
- .rejects
- .toThrow('Message not found');
- });
+ expect(deleteMessage(mockMessageId, mockAuth, mockReq)).rejects.toThrow('Message not found')
+ })
- it('should throw if the message was not broadcasted', async () => {
- const mockMessageId = {
- messageId: 123
- };
- const mockMessage = {
- channel_id: "mockChannelId"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ it('should throw if the message was not broadcasted', async () => {
+ const mockMessageId = {
+ messageId: 123,
+ }
+ const mockMessage = {
+ channel_id: 'mockChannelId',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (messageHelpers.broadcastPrivateMessages as jest.Mock).mockRejectedValue(new Error('Broadcast Error'));
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(messageHelpers.broadcastPrivateMessages as jest.Mock).mockRejectedValue(
+ new Error('Broadcast Error'),
+ )
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
- await deleteMessage(mockMessageId, mockAuth, mockReq);
+ await deleteMessage(mockMessageId, mockAuth, mockReq)
- expect(errorSpy).toBeCalledTimes(1);
- expect(errorSpy).toBeCalledWith(
- expect.stringContaining('broadcastPrivateMessages failed'),
- expect.any(Error)
- );
- });
- });
-});
\ No newline at end of file
+ expect(errorSpy).toBeCalledTimes(1)
+ expect(errorSpy).toBeCalledWith(
+ expect.stringContaining('broadcastPrivateMessages failed'),
+ expect.any(Error),
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/edit-message.unit.test.ts b/backend/api/tests/unit/edit-message.unit.test.ts
index e2b8cbb5..387c46c7 100644
--- a/backend/api/tests/unit/edit-message.unit.test.ts
+++ b/backend/api/tests/unit/edit-message.unit.test.ts
@@ -1,127 +1,130 @@
-jest.mock('shared/supabase/init');
-jest.mock('shared/encryption');
-jest.mock('api/helpers/private-messages');
+jest.mock('shared/supabase/init')
+jest.mock('shared/encryption')
+jest.mock('api/helpers/private-messages')
-import {sqlMatch} from "common/test-utils";
-import {editMessage} from "api/edit-message";
-import * as supabaseInit from "shared/supabase/init";
-import * as encryptionModules from "shared/encryption";
-import * as messageHelpers from "api/helpers/private-messages";
-import {AuthedUser} from "api/helpers/endpoint";
+import {editMessage} from 'api/edit-message'
+import {AuthedUser} from 'api/helpers/endpoint'
+import * as messageHelpers from 'api/helpers/private-messages'
+import {sqlMatch} from 'common/test-utils'
+import * as encryptionModules from 'shared/encryption'
+import * as supabaseInit from 'shared/supabase/init'
describe('editMessage', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- none: jest.fn()
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should edit the messages associated with the messageId', async () => {
- const mockProps = {
- messageId: 123,
- content: {'mockContent' : 'mockContentValue'}
- };
- const mockPlainTextContent = JSON.stringify(mockProps.content)
- const mockMessage = {
- channel_id: "mockChannelId"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockCipher = "mockCipherText";
- const mockIV = "mockIV";
- const mockTag = "mockTag";
- const mockEncryption = {
- ciphertext: mockCipher,
- iv: mockIV,
- tag: mockTag
- };
+ describe('when given valid input', () => {
+ it('should edit the messages associated with the messageId', async () => {
+ const mockProps = {
+ messageId: 123,
+ content: {mockContent: 'mockContentValue'},
+ }
+ const mockPlainTextContent = JSON.stringify(mockProps.content)
+ const mockMessage = {
+ channel_id: 'mockChannelId',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockCipher = 'mockCipherText'
+ const mockIV = 'mockIV'
+ const mockTag = 'mockTag'
+ const mockEncryption = {
+ ciphertext: mockCipher,
+ iv: mockIV,
+ tag: mockTag,
+ }
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage);
- (encryptionModules.encryptMessage as jest.Mock).mockReturnValue(mockEncryption);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (messageHelpers.broadcastPrivateMessages as jest.Mock).mockResolvedValue(null);
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage)
+ ;(encryptionModules.encryptMessage as jest.Mock).mockReturnValue(mockEncryption)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(messageHelpers.broadcastPrivateMessages as jest.Mock).mockResolvedValue(null)
- const result = await editMessage(mockProps, mockAuth, mockReq);
-
- expect(result.success).toBeTruthy();
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('SELECT *'),
- [mockProps.messageId, mockAuth.uid]
- );
- expect(encryptionModules.encryptMessage).toBeCalledTimes(1);
- expect(encryptionModules.encryptMessage).toBeCalledWith(mockPlainTextContent);
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('UPDATE private_user_messages'),
- [mockCipher, mockIV, mockTag, mockProps.messageId]
- );
- expect(messageHelpers.broadcastPrivateMessages).toBeCalledTimes(1);
- expect(messageHelpers.broadcastPrivateMessages).toBeCalledWith(
- expect.any(Object),
- mockMessage.channel_id,
- mockAuth.uid
- );
- });
- });
-
- describe('when an error occurs', () => {
- it('should throw if there is an issue with the message', async () => {
- const mockProps = {
- messageId: 123,
- content: {'mockContent' : 'mockContentValue'}
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ const result = await editMessage(mockProps, mockAuth, mockReq)
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
+ expect(result.success).toBeTruthy()
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(sqlMatch('SELECT *'), [
+ mockProps.messageId,
+ mockAuth.uid,
+ ])
+ expect(encryptionModules.encryptMessage).toBeCalledTimes(1)
+ expect(encryptionModules.encryptMessage).toBeCalledWith(mockPlainTextContent)
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(sqlMatch('UPDATE private_user_messages'), [
+ mockCipher,
+ mockIV,
+ mockTag,
+ mockProps.messageId,
+ ])
+ expect(messageHelpers.broadcastPrivateMessages).toBeCalledTimes(1)
+ expect(messageHelpers.broadcastPrivateMessages).toBeCalledWith(
+ expect.any(Object),
+ mockMessage.channel_id,
+ mockAuth.uid,
+ )
+ })
+ })
- expect(editMessage(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Message not found or cannot be edited');
- });
+ describe('when an error occurs', () => {
+ it('should throw if there is an issue with the message', async () => {
+ const mockProps = {
+ messageId: 123,
+ content: {mockContent: 'mockContentValue'},
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- it('should throw if the message broadcast failed', async () => {
- const mockProps = {
- messageId: 123,
- content: {'mockContent' : 'mockContentValue'}
- };
- const mockMessage = {
- channel_id: "mockChannelId"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockCipher = "mockCipherText";
- const mockIV = "mockIV";
- const mockTag = "mockTag";
- const mockEncryption = {
- ciphertext: mockCipher,
- iv: mockIV,
- tag: mockTag
- };
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage);
- (encryptionModules.encryptMessage as jest.Mock).mockReturnValue(mockEncryption);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (messageHelpers.broadcastPrivateMessages as jest.Mock).mockRejectedValue(new Error('Broadcast Error'));
-
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ expect(editMessage(mockProps, mockAuth, mockReq)).rejects.toThrow(
+ 'Message not found or cannot be edited',
+ )
+ })
- await editMessage(mockProps, mockAuth, mockReq);
- expect(errorSpy).toBeCalledTimes(1);
- expect(errorSpy).toBeCalledWith(
- expect.stringContaining('broadcastPrivateMessages failed'),
- expect.any(Error)
- );
- });
- });
-});
\ No newline at end of file
+ it('should throw if the message broadcast failed', async () => {
+ const mockProps = {
+ messageId: 123,
+ content: {mockContent: 'mockContentValue'},
+ }
+ const mockMessage = {
+ channel_id: 'mockChannelId',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockCipher = 'mockCipherText'
+ const mockIV = 'mockIV'
+ const mockTag = 'mockTag'
+ const mockEncryption = {
+ ciphertext: mockCipher,
+ iv: mockIV,
+ tag: mockTag,
+ }
+
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage)
+ ;(encryptionModules.encryptMessage as jest.Mock).mockReturnValue(mockEncryption)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(messageHelpers.broadcastPrivateMessages as jest.Mock).mockRejectedValue(
+ new Error('Broadcast Error'),
+ )
+
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
+
+ await editMessage(mockProps, mockAuth, mockReq)
+ expect(errorSpy).toBeCalledTimes(1)
+ expect(errorSpy).toBeCalledWith(
+ expect.stringContaining('broadcastPrivateMessages failed'),
+ expect.any(Error),
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/get-compatibility-questions.unit.test.ts b/backend/api/tests/unit/get-compatibility-questions.unit.test.ts
index f29326c4..3f39d986 100644
--- a/backend/api/tests/unit/get-compatibility-questions.unit.test.ts
+++ b/backend/api/tests/unit/get-compatibility-questions.unit.test.ts
@@ -1,56 +1,53 @@
-jest.mock('shared/supabase/init');
+jest.mock('shared/supabase/init')
-import * as compatibililtyQuestionsModules from "api/get-compatibililty-questions";
-import * as supabaseInit from "shared/supabase/init";
+import * as compatibililtyQuestionsModules from 'api/get-compatibililty-questions'
+import * as supabaseInit from 'shared/supabase/init'
describe('getCompatibilityQuestions', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- manyOrNone: jest.fn()
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- describe('when given valid input', () => {
- it('should get compatibility questions', async () => {
- const mockProps = {locale: 'en'} as any;
- const mockAuth = {} as any;
- const mockReq = {} as any;
- const mockQuestions = {
- answer_type: "mockAnswerTypes",
- category: "mockCategory",
- created_time: "mockCreatedTime",
- creator_id: "mockCreatorId",
- id: "mockId",
- importance_score: 123,
- multiple_choice_options: {"mockChoice" : "mockChoiceValue"},
- question: "mockQuestion",
- answer_count: 10,
- score: 20
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ manyOrNone: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (mockPg.manyOrNone as jest.Mock).mockResolvedValue(mockQuestions);
-
- const results: any = await compatibililtyQuestionsModules.getCompatibilityQuestions(mockProps, mockAuth, mockReq);
- const [sql, _params] = (mockPg.manyOrNone as jest.Mock).mock.calls[0];
-
- expect(results.status).toBe('success');
- expect(results.questions).toBe(mockQuestions);
- expect(sql).toEqual(
- expect.stringContaining('FROM compatibility_prompts')
- );
- expect(sql).toEqual(
- expect.stringContaining('LEFT JOIN compatibility_prompts_translations')
- );
- expect(sql).toEqual(
- expect.stringContaining('COUNT(ca.question_id)')
- );
- });
- });
-});
\ No newline at end of file
+ describe('when given valid input', () => {
+ it('should get compatibility questions', async () => {
+ const mockProps = {locale: 'en'} as any
+ const mockAuth = {} as any
+ const mockReq = {} as any
+ const mockQuestions = {
+ answer_type: 'mockAnswerTypes',
+ category: 'mockCategory',
+ created_time: 'mockCreatedTime',
+ creator_id: 'mockCreatorId',
+ id: 'mockId',
+ importance_score: 123,
+ multiple_choice_options: {mockChoice: 'mockChoiceValue'},
+ question: 'mockQuestion',
+ answer_count: 10,
+ score: 20,
+ }
+
+ ;(mockPg.manyOrNone as jest.Mock).mockResolvedValue(mockQuestions)
+
+ const results: any = await compatibililtyQuestionsModules.getCompatibilityQuestions(
+ mockProps,
+ mockAuth,
+ mockReq,
+ )
+ const [sql, _params] = (mockPg.manyOrNone as jest.Mock).mock.calls[0]
+
+ expect(results.status).toBe('success')
+ expect(results.questions).toBe(mockQuestions)
+ expect(sql).toEqual(expect.stringContaining('FROM compatibility_prompts'))
+ expect(sql).toEqual(expect.stringContaining('LEFT JOIN compatibility_prompts_translations'))
+ expect(sql).toEqual(expect.stringContaining('COUNT(ca.question_id)'))
+ })
+ })
+})
diff --git a/backend/api/tests/unit/get-current-private-users.unit.test.ts b/backend/api/tests/unit/get-current-private-users.unit.test.ts
index 8d83bddb..b326d751 100644
--- a/backend/api/tests/unit/get-current-private-users.unit.test.ts
+++ b/backend/api/tests/unit/get-current-private-users.unit.test.ts
@@ -1,76 +1,75 @@
-import {sqlMatch} from "common/test-utils";
-import {getCurrentPrivateUser} from "api/get-current-private-user";
-import * as supabaseInit from "shared/supabase/init";
-import {tryCatch} from "common/util/try-catch";
-import {AuthedUser} from "api/helpers/endpoint";
+import {getCurrentPrivateUser} from 'api/get-current-private-user'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {sqlMatch} from 'common/test-utils'
+import {tryCatch} from 'common/util/try-catch'
+import * as supabaseInit from 'shared/supabase/init'
-jest.mock('shared/supabase/init');
-jest.mock('common/util/try-catch');
+jest.mock('shared/supabase/init')
+jest.mock('common/util/try-catch')
describe('getCurrentPrivateUser', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn()
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should get current private user', async () => {
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockProps = {} as any;
- const mockReq = {} as any;
- const mockData = {
- data: {"mockData" : "mockDataValue"},
- id: "mockId"
- };
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({data: mockData, error: null});
+ describe('when given valid input', () => {
+ it('should get current private user', async () => {
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockProps = {} as any
+ const mockReq = {} as any
+ const mockData = {
+ data: {mockData: 'mockDataValue'},
+ id: 'mockId',
+ }
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockData, error: null})
- const result = await getCurrentPrivateUser(mockProps, mockAuth, mockReq);
-
- expect(result).toBe(mockData.data);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select * from private_users where id = $1'),
- [mockAuth.uid]
- );
- });
- });
-
- describe('when an error occurs', () => {
- it('should throw if unable to get users private data', async () => {
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockProps = {} as any;
- const mockReq = {} as any;
- const mockData = {
- data: {"mockData" : "mockDataValue"},
- id: "mockId"
- };
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({data: mockData, error: Error});
+ const result = await getCurrentPrivateUser(mockProps, mockAuth, mockReq)
- expect(getCurrentPrivateUser(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Error fetching private user data: ');
- });
+ expect(result).toBe(mockData.data)
+ expect(mockPg.oneOrNone).toBeCalledWith(
+ sqlMatch('select * from private_users where id = $1'),
+ [mockAuth.uid],
+ )
+ })
+ })
- it('should throw if unable to find user account', async () => {
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockProps = {} as any;
- const mockReq = {} as any;
+ describe('when an error occurs', () => {
+ it('should throw if unable to get users private data', async () => {
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockProps = {} as any
+ const mockReq = {} as any
+ const mockData = {
+ data: {mockData: 'mockDataValue'},
+ id: 'mockId',
+ }
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockData, error: Error})
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({data: null, error: null});
+ expect(getCurrentPrivateUser(mockProps, mockAuth, mockReq)).rejects.toThrow(
+ 'Error fetching private user data: ',
+ )
+ })
- expect(getCurrentPrivateUser(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Your account was not found');
- });
- });
-});
\ No newline at end of file
+ it('should throw if unable to find user account', async () => {
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockProps = {} as any
+ const mockReq = {} as any
+
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: null, error: null})
+
+ expect(getCurrentPrivateUser(mockProps, mockAuth, mockReq)).rejects.toThrow(
+ 'Your account was not found',
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/get-likes-and-ships.unit.test.ts b/backend/api/tests/unit/get-likes-and-ships.unit.test.ts
index d0c7341c..37464c26 100644
--- a/backend/api/tests/unit/get-likes-and-ships.unit.test.ts
+++ b/backend/api/tests/unit/get-likes-and-ships.unit.test.ts
@@ -1,82 +1,80 @@
-jest.mock('shared/supabase/init');
+jest.mock('shared/supabase/init')
-import * as likesAndShips from "api/get-likes-and-ships";
-import { AuthedUser } from "api/helpers/endpoint";
-import * as supabaseInit from "shared/supabase/init";
+import * as likesAndShips from 'api/get-likes-and-ships'
+import {AuthedUser} from 'api/helpers/endpoint'
+import * as supabaseInit from 'shared/supabase/init'
describe('getLikesAndShips', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- map: jest.fn(),
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ map: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should get all likes recieved/given an any ships', async () => {
- const mockProps = {userId: "mockUserId"};
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockLikesGiven = {
- user_id: "mockUser_Id_likes_given",
- created_Time: 123
- };
- const mockLikesReceived = {
- user_id: "mockUser_Id_likes_received",
- created_Time: 1234
- };
- const mockShips = {
- creator_id: "mockCreatorId",
- target_id: "mockTargetId",
- target1_id: "mockTarget1Id",
- target2_id: "mockTarget2Id",
- target3_id: "mockTarget3Id",
- created_time: 12345
- };
+ describe('when given valid input', () => {
+ it('should get all likes recieved/given an any ships', async () => {
+ const mockProps = {userId: 'mockUserId'}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockLikesGiven = {
+ user_id: 'mockUser_Id_likes_given',
+ created_Time: 123,
+ }
+ const mockLikesReceived = {
+ user_id: 'mockUser_Id_likes_received',
+ created_Time: 1234,
+ }
+ const mockShips = {
+ creator_id: 'mockCreatorId',
+ target_id: 'mockTargetId',
+ target1_id: 'mockTarget1Id',
+ target2_id: 'mockTarget2Id',
+ target3_id: 'mockTarget3Id',
+ created_time: 12345,
+ }
- jest.spyOn(likesAndShips, 'getLikesAndShipsMain');
- (mockPg.map as jest.Mock)
- .mockResolvedValueOnce(mockLikesGiven)
- .mockResolvedValueOnce(mockLikesReceived)
- .mockResolvedValueOnce(mockShips);
+ jest.spyOn(likesAndShips, 'getLikesAndShipsMain')
+ ;(mockPg.map as jest.Mock)
+ .mockResolvedValueOnce(mockLikesGiven)
+ .mockResolvedValueOnce(mockLikesReceived)
+ .mockResolvedValueOnce(mockShips)
+ const result: any = await likesAndShips.getLikesAndShips(mockProps, mockAuth, mockReq)
+ const [sql1, _params1, _fn1] = (mockPg.map as jest.Mock).mock.calls[0]
+ const [sql2, _params2, _fn2] = (mockPg.map as jest.Mock).mock.calls[1]
+ const [sql3, _params3, _fn3] = (mockPg.map as jest.Mock).mock.calls[2]
- const result: any = await likesAndShips.getLikesAndShips(mockProps, mockAuth, mockReq);
- const [sql1, params1, fn1] = (mockPg.map as jest.Mock).mock.calls[0];
- const [sql2, params2, fn2] = (mockPg.map as jest.Mock).mock.calls[1];
- const [sql3, params3, fn3] = (mockPg.map as jest.Mock).mock.calls[2];
+ expect(result.status).toBe('success')
+ expect(result.likesGiven).toBe(mockLikesGiven)
+ expect(result.likesReceived).toBe(mockLikesReceived)
+ expect(result.ships).toBe(mockShips)
- expect(result.status).toBe('success');
- expect(result.likesGiven).toBe(mockLikesGiven);
- expect(result.likesReceived).toBe(mockLikesReceived);
- expect(result.ships).toBe(mockShips);
-
- expect(likesAndShips.getLikesAndShipsMain).toBeCalledTimes(1);
- expect(likesAndShips.getLikesAndShipsMain).toBeCalledWith(mockProps.userId);
- expect(mockPg.map).toHaveBeenNthCalledWith(
- 1,
- expect.stringContaining(sql1),
- [mockProps.userId],
- expect.any(Function)
- );
- expect(mockPg.map).toHaveBeenNthCalledWith(
- 2,
- expect.stringContaining(sql2),
- [mockProps.userId],
- expect.any(Function)
- );
- expect(mockPg.map).toHaveBeenNthCalledWith(
- 3,
- expect.stringContaining(sql3),
- [mockProps.userId],
- expect.any(Function)
- );
- });
- });
-});
\ No newline at end of file
+ expect(likesAndShips.getLikesAndShipsMain).toBeCalledTimes(1)
+ expect(likesAndShips.getLikesAndShipsMain).toBeCalledWith(mockProps.userId)
+ expect(mockPg.map).toHaveBeenNthCalledWith(
+ 1,
+ expect.stringContaining(sql1),
+ [mockProps.userId],
+ expect.any(Function),
+ )
+ expect(mockPg.map).toHaveBeenNthCalledWith(
+ 2,
+ expect.stringContaining(sql2),
+ [mockProps.userId],
+ expect.any(Function),
+ )
+ expect(mockPg.map).toHaveBeenNthCalledWith(
+ 3,
+ expect.stringContaining(sql3),
+ [mockProps.userId],
+ expect.any(Function),
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/get-me.unit.test.ts b/backend/api/tests/unit/get-me.unit.test.ts
index a2d1f175..9a83853c 100644
--- a/backend/api/tests/unit/get-me.unit.test.ts
+++ b/backend/api/tests/unit/get-me.unit.test.ts
@@ -1,29 +1,29 @@
-jest.mock('api/get-user');
+jest.mock('api/get-user')
-import { getMe } from "api/get-me";
-import { getUser } from "api/get-user";
-import { AuthedUser } from "api/helpers/endpoint";
+import {getMe} from 'api/get-me'
+import {getUser} from 'api/get-user'
+import {AuthedUser} from 'api/helpers/endpoint'
describe('getMe', () => {
- beforeEach(() => {
- jest.resetAllMocks();
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ beforeEach(() => {
+ jest.resetAllMocks()
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should get the user', async () => {
- const mockProps = {};
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ describe('when given valid input', () => {
+ it('should get the user', async () => {
+ const mockProps = {}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (getUser as jest.Mock).mockResolvedValue(null);
+ ;(getUser as jest.Mock).mockResolvedValue(null)
- await getMe(mockProps, mockAuth, mockReq);
+ await getMe(mockProps, mockAuth, mockReq)
- expect(getUser).toBeCalledTimes(1);
- expect(getUser).toBeCalledWith({id: mockAuth.uid});
- });
- });
-});
\ No newline at end of file
+ expect(getUser).toBeCalledTimes(1)
+ expect(getUser).toBeCalledWith({id: mockAuth.uid})
+ })
+ })
+})
diff --git a/backend/api/tests/unit/get-messages-count.unit.test.ts b/backend/api/tests/unit/get-messages-count.unit.test.ts
index 63f14ee4..624c8a91 100644
--- a/backend/api/tests/unit/get-messages-count.unit.test.ts
+++ b/backend/api/tests/unit/get-messages-count.unit.test.ts
@@ -1,41 +1,37 @@
-import {sqlMatch} from "common/test-utils";
-import {getMessagesCount} from "api/get-messages-count";
-import {AuthedUser} from "api/helpers/endpoint";
-import * as supabaseInit from "shared/supabase/init";
+import {getMessagesCount} from 'api/get-messages-count'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
-jest.mock('shared/supabase/init');
+jest.mock('shared/supabase/init')
describe('getMessagesCount', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- one: jest.fn()
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ one: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should get message count', async () => {
- const mockProps = {} as any;
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockResults = { count: "10"};
+ describe('when given valid input', () => {
+ it('should get message count', async () => {
+ const mockProps = {} as any
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockResults = {count: '10'}
- (mockPg.one as jest.Mock).mockResolvedValue(mockResults);
+ ;(mockPg.one as jest.Mock).mockResolvedValue(mockResults)
- const result: any = await getMessagesCount(mockProps, mockAuth, mockReq);
+ const result: any = await getMessagesCount(mockProps, mockAuth, mockReq)
- expect(result.count).toBe(Number(mockResults.count));
- expect(mockPg.one).toBeCalledTimes(1);
- expect(mockPg.one).toBeCalledWith(
- sqlMatch('SELECT COUNT(*) AS count'),
- expect.any(Object)
- );
- });
- });
-});
\ No newline at end of file
+ expect(result.count).toBe(Number(mockResults.count))
+ expect(mockPg.one).toBeCalledTimes(1)
+ expect(mockPg.one).toBeCalledWith(sqlMatch('SELECT COUNT(*) AS count'), expect.any(Object))
+ })
+ })
+})
diff --git a/backend/api/tests/unit/get-notifications.unit.test.ts b/backend/api/tests/unit/get-notifications.unit.test.ts
index 36bc3a1e..4281200f 100644
--- a/backend/api/tests/unit/get-notifications.unit.test.ts
+++ b/backend/api/tests/unit/get-notifications.unit.test.ts
@@ -1,45 +1,46 @@
-jest.mock('shared/supabase/init');
+jest.mock('shared/supabase/init')
-import {getNotifications} from "api/get-notifications";
-import {AuthedUser} from "api/helpers/endpoint";
-import * as supabaseInit from "shared/supabase/init";
+import {getNotifications} from 'api/get-notifications'
+import {AuthedUser} from 'api/helpers/endpoint'
import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
describe('getNotifications', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- map: jest.fn()
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ map: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should send user notifications', async () => {
- const mockProps = {
- limit: 10,
- after: 2
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockNotifications = {} as any;
+ describe('when given valid input', () => {
+ it('should send user notifications', async () => {
+ const mockProps = {
+ limit: 10,
+ after: 2,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockNotifications = {} as any
- (mockPg.map as jest.Mock).mockResolvedValue(mockNotifications);
+ ;(mockPg.map as jest.Mock).mockResolvedValue(mockNotifications)
- const result = await getNotifications(mockProps, mockAuth, mockReq);
-
- expect(result).toBe(mockNotifications);
- expect(mockPg.map).toBeCalledTimes(1);
- expect(mockPg.map).toBeCalledWith(
- sqlMatch('from user_notifications un left join notification_templates nt on un.template_id = nt.id'),
- [mockAuth.uid, mockProps.limit, mockProps.after],
- expect.any(Function)
- );
- });
- });
-});
\ No newline at end of file
+ const result = await getNotifications(mockProps, mockAuth, mockReq)
+
+ expect(result).toBe(mockNotifications)
+ expect(mockPg.map).toBeCalledTimes(1)
+ expect(mockPg.map).toBeCalledWith(
+ sqlMatch(
+ 'from user_notifications un left join notification_templates nt on un.template_id = nt.id',
+ ),
+ [mockAuth.uid, mockProps.limit, mockProps.after],
+ expect.any(Function),
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/get-options.unit.test.ts b/backend/api/tests/unit/get-options.unit.test.ts
index d234f948..25ec0266 100644
--- a/backend/api/tests/unit/get-options.unit.test.ts
+++ b/backend/api/tests/unit/get-options.unit.test.ts
@@ -1,75 +1,68 @@
-jest.mock('shared/supabase/init');
-jest.mock('common/util/try-catch');
+jest.mock('shared/supabase/init')
+jest.mock('common/util/try-catch')
-import {sqlMatch} from "common/test-utils";
-import {getOptions} from "api/get-options";
-import * as supabaseInit from "shared/supabase/init";
-import {tryCatch} from "common/util/try-catch";
-import {AuthedUser} from "api/helpers/endpoint";
+import {getOptions} from 'api/get-options'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {sqlMatch} from 'common/test-utils'
+import {tryCatch} from 'common/util/try-catch'
+import * as supabaseInit from 'shared/supabase/init'
describe('getOptions', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- manyOrNone: jest.fn(),
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ manyOrNone: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should return valid options', async () => {
- const mockTable = "causes";
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockData = [
- { name: "mockName" },
- ];
+ describe('when given valid input', () => {
+ it('should return valid options', async () => {
+ const mockTable = 'causes'
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockData = [{name: 'mockName'}]
- jest.spyOn(Array.prototype, 'includes').mockReturnValue(true);
- (mockPg.manyOrNone as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({data: mockData, error: null});
+ jest.spyOn(Array.prototype, 'includes').mockReturnValue(true)
+ ;(mockPg.manyOrNone as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockData, error: null})
- const result: any = await getOptions({table: mockTable}, mockAuth, mockReq);
+ const result: any = await getOptions({table: mockTable}, mockAuth, mockReq)
- expect(result.names).toContain(mockData[0].name);
- expect(mockPg.manyOrNone).toBeCalledTimes(1);
- expect(mockPg.manyOrNone).toBeCalledWith(
- sqlMatch('SELECT interests.name')
- );
- expect(tryCatch).toBeCalledTimes(1);
- });
- });
+ expect(result.names).toContain(mockData[0].name)
+ expect(mockPg.manyOrNone).toBeCalledTimes(1)
+ expect(mockPg.manyOrNone).toBeCalledWith(sqlMatch('SELECT interests.name'))
+ expect(tryCatch).toBeCalledTimes(1)
+ })
+ })
- describe('when an error occurs', () => {
- it('should throw if the table is invalid', async () => {
- const mockTable = "causes";
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ describe('when an error occurs', () => {
+ it('should throw if the table is invalid', async () => {
+ const mockTable = 'causes'
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- jest.spyOn(Array.prototype, 'includes').mockReturnValue(false);
+ jest.spyOn(Array.prototype, 'includes').mockReturnValue(false)
- expect(getOptions({table: mockTable}, mockAuth, mockReq))
- .rejects
- .toThrow('Invalid table');
- });
+ expect(getOptions({table: mockTable}, mockAuth, mockReq)).rejects.toThrow('Invalid table')
+ })
- it('should throw if unable to get profile options', async () => {
- const mockTable = "causes";
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ it('should throw if unable to get profile options', async () => {
+ const mockTable = 'causes'
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- jest.spyOn(Array.prototype, 'includes').mockReturnValue(true);
- (mockPg.manyOrNone as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error});
+ jest.spyOn(Array.prototype, 'includes').mockReturnValue(true)
+ ;(mockPg.manyOrNone as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error})
- expect(getOptions({table: mockTable}, mockAuth, mockReq))
- .rejects
- .toThrow('Error getting profile options');
- });
- });
-});
\ No newline at end of file
+ expect(getOptions({table: mockTable}, mockAuth, mockReq)).rejects.toThrow(
+ 'Error getting profile options',
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/get-private-messages.unit.test.ts b/backend/api/tests/unit/get-private-messages.unit.test.ts
index 13fec05c..c0663d68 100644
--- a/backend/api/tests/unit/get-private-messages.unit.test.ts
+++ b/backend/api/tests/unit/get-private-messages.unit.test.ts
@@ -1,290 +1,296 @@
-import {sqlMatch} from "common/test-utils";
-import * as getPrivateMessages from "api/get-private-messages";
-import * as supabaseInit from "shared/supabase/init";
-import {tryCatch} from "common/util/try-catch";
-import {AuthedUser} from "api/helpers/endpoint";
+import * as getPrivateMessages from 'api/get-private-messages'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {sqlMatch} from 'common/test-utils'
+import {tryCatch} from 'common/util/try-catch'
+import * as supabaseInit from 'shared/supabase/init'
-jest.mock('shared/supabase/init');
-jest.mock('common/util/try-catch');
-jest.mock('shared/supabase/messages');
+jest.mock('shared/supabase/init')
+jest.mock('common/util/try-catch')
+jest.mock('shared/supabase/messages')
describe('getChannelMemberships', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- map: jest.fn(),
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ map: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should return channel memberships', async () => {
- const mockProps = {
- limit: 10,
- channelId: 1,
- createdTime: "mockCreatedTime",
- lastUpdatedTime: "mockLastUpdatedTime"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockChannels = [
- {
- channel_id: 123,
- notify_after_time: "mockNotifyAfterTime",
- created_time: "mockCreatedTime",
- last_updated_time: "mockLastUpdatedTime"
- }
- ];
- const mockMembers = [
- {
- channel_id: 1234,
- user_id: "mockUserId"
- }
- ];
- (mockPg.map as jest.Mock)
- .mockResolvedValueOnce(mockChannels)
- .mockResolvedValueOnce(mockMembers);
-
- const results: any = await getPrivateMessages.getChannelMemberships(mockProps, mockAuth, mockReq);
-
- expect(results.channels).toBe(mockChannels);
- expect(Object.keys(results.memberIdsByChannelId)[0]).toBe(String(mockMembers[0].channel_id));
- expect(Object.values(results.memberIdsByChannelId)[0]).toContain(mockMembers[0].user_id);
+ describe('when given valid input', () => {
+ it('should return channel memberships', async () => {
+ const mockProps = {
+ limit: 10,
+ channelId: 1,
+ createdTime: 'mockCreatedTime',
+ lastUpdatedTime: 'mockLastUpdatedTime',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockChannels = [
+ {
+ channel_id: 123,
+ notify_after_time: 'mockNotifyAfterTime',
+ created_time: 'mockCreatedTime',
+ last_updated_time: 'mockLastUpdatedTime',
+ },
+ ]
+ const mockMembers = [
+ {
+ channel_id: 1234,
+ user_id: 'mockUserId',
+ },
+ ]
+ ;(mockPg.map as jest.Mock)
+ .mockResolvedValueOnce(mockChannels)
+ .mockResolvedValueOnce(mockMembers)
- expect(mockPg.map).toBeCalledTimes(2);
- expect(mockPg.map).toHaveBeenNthCalledWith(
- 1,
- sqlMatch('select channel_id, notify_after_time, pumcm.created_time, last_updated_time'),
- [mockAuth.uid, mockProps.channelId, mockProps.limit],
- expect.any(Function)
- );
- expect(mockPg.map).toHaveBeenNthCalledWith(
- 2,
- sqlMatch('select channel_id, user_id'),
- [mockAuth.uid, [mockChannels[0].channel_id]],
- expect.any(Function)
- );
- });
+ const results: any = await getPrivateMessages.getChannelMemberships(
+ mockProps,
+ mockAuth,
+ mockReq,
+ )
- it('should return channel memberships if there is no channelId', async () => {
- const mockProps = {
- limit: 10,
- createdTime: "mockCreatedTime",
- lastUpdatedTime: "mockLastUpdatedTime"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockChannels = [
- {
- channel_id: 123,
- notify_after_time: "mockNotifyAfterTime",
- created_time: "mockCreatedTime",
- last_updated_time: "mockLastUpdatedTime"
- }
- ];
- const mockMembers = [
- {
- channel_id: 1234,
- user_id: "mockUserId"
- }
- ];
- (mockPg.map as jest.Mock)
- .mockResolvedValueOnce(mockChannels)
- .mockResolvedValueOnce(mockMembers);
-
- const results: any = await getPrivateMessages.getChannelMemberships(mockProps, mockAuth, mockReq);
-
- expect(results.channels).toBe(mockChannels);
- expect(Object.keys(results.memberIdsByChannelId)[0]).toBe(String(mockMembers[0].channel_id));
- expect(Object.values(results.memberIdsByChannelId)[0]).toContain(mockMembers[0].user_id);
+ expect(results.channels).toBe(mockChannels)
+ expect(Object.keys(results.memberIdsByChannelId)[0]).toBe(String(mockMembers[0].channel_id))
+ expect(Object.values(results.memberIdsByChannelId)[0]).toContain(mockMembers[0].user_id)
- expect(mockPg.map).toBeCalledTimes(2);
- expect(mockPg.map).toHaveBeenNthCalledWith(
- 1,
- sqlMatch('with latest_channels as (select distinct on (pumc.id) pumc.id as channel_id'),
- [mockAuth.uid, mockProps.createdTime, mockProps.limit, mockProps.lastUpdatedTime],
- expect.any(Function)
- );
- expect(mockPg.map).toHaveBeenNthCalledWith(
- 2,
- sqlMatch('select channel_id, user_id'),
- [mockAuth.uid, [mockChannels[0].channel_id]],
- expect.any(Function)
- );
- });
+ expect(mockPg.map).toBeCalledTimes(2)
+ expect(mockPg.map).toHaveBeenNthCalledWith(
+ 1,
+ sqlMatch('select channel_id, notify_after_time, pumcm.created_time, last_updated_time'),
+ [mockAuth.uid, mockProps.channelId, mockProps.limit],
+ expect.any(Function),
+ )
+ expect(mockPg.map).toHaveBeenNthCalledWith(
+ 2,
+ sqlMatch('select channel_id, user_id'),
+ [mockAuth.uid, [mockChannels[0].channel_id]],
+ expect.any(Function),
+ )
+ })
- it('should return nothing if there are no channels', async () => {
- const mockProps = {
- limit: 10,
- channelId: 1,
- createdTime: "mockCreatedTime",
- lastUpdatedTime: "mockLastUpdatedTime"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ it('should return channel memberships if there is no channelId', async () => {
+ const mockProps = {
+ limit: 10,
+ createdTime: 'mockCreatedTime',
+ lastUpdatedTime: 'mockLastUpdatedTime',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockChannels = [
+ {
+ channel_id: 123,
+ notify_after_time: 'mockNotifyAfterTime',
+ created_time: 'mockCreatedTime',
+ last_updated_time: 'mockLastUpdatedTime',
+ },
+ ]
+ const mockMembers = [
+ {
+ channel_id: 1234,
+ user_id: 'mockUserId',
+ },
+ ]
+ ;(mockPg.map as jest.Mock)
+ .mockResolvedValueOnce(mockChannels)
+ .mockResolvedValueOnce(mockMembers)
- (mockPg.map as jest.Mock).mockResolvedValueOnce(null);
-
- const results: any = await getPrivateMessages.getChannelMemberships(mockProps, mockAuth, mockReq);
-
- console.log(results);
-
- expect(results).toStrictEqual({ channels: [], memberIdsByChannelId: {} });
+ const results: any = await getPrivateMessages.getChannelMemberships(
+ mockProps,
+ mockAuth,
+ mockReq,
+ )
- expect(mockPg.map).toBeCalledTimes(1);
- expect(mockPg.map).toHaveBeenNthCalledWith(
- 1,
- sqlMatch('select channel_id, notify_after_time, pumcm.created_time, last_updated_time'),
- [mockAuth.uid, mockProps.channelId, mockProps.limit],
- expect.any(Function)
- );
- });
- });
-});
+ expect(results.channels).toBe(mockChannels)
+ expect(Object.keys(results.memberIdsByChannelId)[0]).toBe(String(mockMembers[0].channel_id))
+ expect(Object.values(results.memberIdsByChannelId)[0]).toContain(mockMembers[0].user_id)
+
+ expect(mockPg.map).toBeCalledTimes(2)
+ expect(mockPg.map).toHaveBeenNthCalledWith(
+ 1,
+ sqlMatch(
+ 'with latest_channels as (select distinct on (pumc.id) pumc.id as channel_id',
+ ),
+ [mockAuth.uid, mockProps.createdTime, mockProps.limit, mockProps.lastUpdatedTime],
+ expect.any(Function),
+ )
+ expect(mockPg.map).toHaveBeenNthCalledWith(
+ 2,
+ sqlMatch('select channel_id, user_id'),
+ [mockAuth.uid, [mockChannels[0].channel_id]],
+ expect.any(Function),
+ )
+ })
+
+ it('should return nothing if there are no channels', async () => {
+ const mockProps = {
+ limit: 10,
+ channelId: 1,
+ createdTime: 'mockCreatedTime',
+ lastUpdatedTime: 'mockLastUpdatedTime',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+
+ ;(mockPg.map as jest.Mock).mockResolvedValueOnce(null)
+
+ const results: any = await getPrivateMessages.getChannelMemberships(
+ mockProps,
+ mockAuth,
+ mockReq,
+ )
+
+ console.log(results)
+
+ expect(results).toStrictEqual({channels: [], memberIdsByChannelId: {}})
+
+ expect(mockPg.map).toBeCalledTimes(1)
+ expect(mockPg.map).toHaveBeenNthCalledWith(
+ 1,
+ sqlMatch('select channel_id, notify_after_time, pumcm.created_time, last_updated_time'),
+ [mockAuth.uid, mockProps.channelId, mockProps.limit],
+ expect.any(Function),
+ )
+ })
+ })
+})
describe('getChannelMessagesEndpoint', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- map: jest.fn(),
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- describe('when given valid input', () => {
- it('should return the channel messages endpoint', async () => {
- const mockProps = {
- limit: 10,
- channelId: 1,
- id: 123
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockData = ['mockResult'] as any;
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ map: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (mockPg.map as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({data: mockData, error: null});
+ describe('when given valid input', () => {
+ it('should return the channel messages endpoint', async () => {
+ const mockProps = {
+ limit: 10,
+ channelId: 1,
+ id: 123,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockData = ['mockResult'] as any
- const result = await getPrivateMessages.getChannelMessagesEndpoint(mockProps, mockAuth, mockReq);
-
- expect(result).toBe(mockData);
- expect(mockPg.map).toBeCalledTimes(1);
- expect(mockPg.map).toBeCalledWith(
- sqlMatch('select *, created_time as created_time_ts'),
- [mockProps.channelId, mockAuth.uid, mockProps.limit, mockProps.id],
- expect.any(Function)
- );
+ ;(mockPg.map as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockData, error: null})
- });
- });
-
- describe('when an error occurs', () => {
- it('should throw if unable to get messages', async () => {
- const mockProps = {
- limit: 10,
- channelId: 1,
- id: 123
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockData = ['mockResult'] as any;
+ const result = await getPrivateMessages.getChannelMessagesEndpoint(
+ mockProps,
+ mockAuth,
+ mockReq,
+ )
- (mockPg.map as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error});
+ expect(result).toBe(mockData)
+ expect(mockPg.map).toBeCalledTimes(1)
+ expect(mockPg.map).toBeCalledWith(
+ sqlMatch('select *, created_time as created_time_ts'),
+ [mockProps.channelId, mockAuth.uid, mockProps.limit, mockProps.id],
+ expect.any(Function),
+ )
+ })
+ })
- expect(getPrivateMessages.getChannelMessagesEndpoint(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Error getting messages');
- });
- });
-});
+ describe('when an error occurs', () => {
+ it('should throw if unable to get messages', async () => {
+ const mockProps = {
+ limit: 10,
+ channelId: 1,
+ id: 123,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const _mockData = ['mockResult'] as any
+
+ ;(mockPg.map as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error})
+
+ expect(
+ getPrivateMessages.getChannelMessagesEndpoint(mockProps, mockAuth, mockReq),
+ ).rejects.toThrow('Error getting messages')
+ })
+ })
+})
describe('getLastSeenChannelTime', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- map: jest.fn(),
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- describe('when given valid input', () => {
- it('should return the last seen channel time', async () => {
- const mockProps = {
- channelIds: [
- 1,
- 2,
- 3,
- ]
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockUnseens = [
- [1, "mockString"]
- ];
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ map: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (mockPg.map as jest.Mock).mockResolvedValue(mockUnseens);
+ describe('when given valid input', () => {
+ it('should return the last seen channel time', async () => {
+ const mockProps = {
+ channelIds: [1, 2, 3],
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockUnseens = [[1, 'mockString']]
- const result = await getPrivateMessages.getLastSeenChannelTime(mockProps, mockAuth, mockReq);
+ ;(mockPg.map as jest.Mock).mockResolvedValue(mockUnseens)
- expect(result).toBe(mockUnseens);
- expect(mockPg.map).toBeCalledTimes(1);
- expect(mockPg.map).toBeCalledWith(
- sqlMatch('select distinct on (channel_id) channel_id, created_time'),
- [mockProps.channelIds, mockAuth.uid],
- expect.any(Function)
- );
+ const result = await getPrivateMessages.getLastSeenChannelTime(mockProps, mockAuth, mockReq)
- });
- });
-});
+ expect(result).toBe(mockUnseens)
+ expect(mockPg.map).toBeCalledTimes(1)
+ expect(mockPg.map).toBeCalledWith(
+ sqlMatch('select distinct on (channel_id) channel_id, created_time'),
+ [mockProps.channelIds, mockAuth.uid],
+ expect.any(Function),
+ )
+ })
+ })
+})
describe('setChannelLastSeenTime', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- none: jest.fn(),
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
-
- describe('when given valid input', () => {
- it('should set channel last seen time', async () => {
- const mockProps = {
- channelId: 1
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (mockPg.none as jest.Mock).mockResolvedValue(null);
+ describe('when given valid input', () => {
+ it('should set channel last seen time', async () => {
+ const mockProps = {
+ channelId: 1,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- await getPrivateMessages.setChannelLastSeenTime(mockProps, mockAuth, mockReq);
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('insert into private_user_seen_message_channels (user_id, channel_id)'),
- [mockAuth.uid, mockProps.channelId]
- );
- });
- });
-});
\ No newline at end of file
+ await getPrivateMessages.setChannelLastSeenTime(mockProps, mockAuth, mockReq)
+
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(
+ sqlMatch('insert into private_user_seen_message_channels (user_id, channel_id)'),
+ [mockAuth.uid, mockProps.channelId],
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/get-profile-answers.unit.test.ts b/backend/api/tests/unit/get-profile-answers.unit.test.ts
index bed2f091..e3aadb8f 100644
--- a/backend/api/tests/unit/get-profile-answers.unit.test.ts
+++ b/backend/api/tests/unit/get-profile-answers.unit.test.ts
@@ -1,54 +1,51 @@
-import {sqlMatch} from "common/test-utils";
-import {getProfileAnswers} from "api/get-profile-answers";
-import {AuthedUser} from "api/helpers/endpoint";
-import * as supabaseInit from "shared/supabase/init";
+import {getProfileAnswers} from 'api/get-profile-answers'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
-jest.mock('shared/supabase/init');
+jest.mock('shared/supabase/init')
describe('getProfileAnswers', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- manyOrNone: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ manyOrNone: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should get the answers for the userId', async () => {
+ const mockProps = {userId: 'mockUserId'}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockAnswers = [
+ {
+ created_time: 'mockCreatedTime',
+ creator_id: 'mockCreatorId',
+ explanation: 'mockExplanation',
+ id: 123,
+ importance: 10,
+ multiple_choice: 1234,
+ pref_choices: [1, 2, 3],
+ question_id: 12345,
+ },
+ ]
- describe('when given valid input', () => {
- it('should get the answers for the userId', async () => {
- const mockProps = { userId: "mockUserId" };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockAnswers = [
- {
- created_time: "mockCreatedTime",
- creator_id: "mockCreatorId",
- explanation: "mockExplanation",
- id: 123,
- importance: 10,
- multiple_choice: 1234,
- pref_choices: [1, 2, 3],
- question_id: 12345
- }
- ];
+ ;(mockPg.manyOrNone as jest.Mock).mockResolvedValue(mockAnswers)
- (mockPg.manyOrNone as jest.Mock).mockResolvedValue(mockAnswers);
+ const result: any = await getProfileAnswers(mockProps, mockAuth, mockReq)
- const result: any = await getProfileAnswers(mockProps, mockAuth, mockReq);
-
- expect(result.status).toBe('success');
- expect(result.answers).toBe(mockAnswers);
- expect(mockPg.manyOrNone).toBeCalledTimes(1);
- expect(mockPg.manyOrNone).toBeCalledWith(
- sqlMatch('select * from compatibility_answers'),
- [mockProps.userId]
- );
- });
- });
-});
\ No newline at end of file
+ expect(result.status).toBe('success')
+ expect(result.answers).toBe(mockAnswers)
+ expect(mockPg.manyOrNone).toBeCalledTimes(1)
+ expect(mockPg.manyOrNone).toBeCalledWith(sqlMatch('select * from compatibility_answers'), [
+ mockProps.userId,
+ ])
+ })
+ })
+})
diff --git a/backend/api/tests/unit/get-profiles.unit.test.ts b/backend/api/tests/unit/get-profiles.unit.test.ts
index 2e863063..f76b4290 100644
--- a/backend/api/tests/unit/get-profiles.unit.test.ts
+++ b/backend/api/tests/unit/get-profiles.unit.test.ts
@@ -1,328 +1,327 @@
-import * as profilesModule from "api/get-profiles";
-import { Profile } from "common/profiles/profile";
-import * as supabaseInit from "shared/supabase/init";
-import * as sqlBuilder from "shared/supabase/sql-builder";
+import * as profilesModule from 'api/get-profiles'
+import {Profile} from 'common/profiles/profile'
+import * as supabaseInit from 'shared/supabase/init'
+import * as sqlBuilder from 'shared/supabase/sql-builder'
describe('getProfiles', () => {
- beforeEach(() => {
- jest.clearAllMocks();
- });
-
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ beforeEach(() => {
+ jest.clearAllMocks()
+ })
- describe('when given valid input', () => {
- it('should successfully return profile information and count', async ()=> {
- const mockProfiles = [
- {
- diet: ['Jonathon Hammon'],
- has_kids: 0
- },
- {
- diet: ['Joseph Hammon'],
- has_kids: 1
- },
- {
- diet: ['Jolene Hammon'],
- has_kids: 2,
- }
- ] as Profile [];
- const props = {
- limit: 2,
- orderBy: "last_online_time" as const,
- };
- const mockReq = {} as any;
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- jest.spyOn(profilesModule, 'loadProfiles').mockResolvedValue({profiles: mockProfiles, count: 3});
-
- const results: any = await profilesModule.getProfiles(props, mockReq, mockReq);
+ describe('when given valid input', () => {
+ it('should successfully return profile information and count', async () => {
+ const mockProfiles = [
+ {
+ diet: ['Jonathon Hammon'],
+ has_kids: 0,
+ },
+ {
+ diet: ['Joseph Hammon'],
+ has_kids: 1,
+ },
+ {
+ diet: ['Jolene Hammon'],
+ has_kids: 2,
+ },
+ ] as Profile[]
+ const props = {
+ limit: 2,
+ orderBy: 'last_online_time' as const,
+ }
+ const mockReq = {} as any
- expect(results.status).toEqual('success');
- expect(results.profiles).toEqual(mockProfiles);
- expect(results.profiles[0]).toEqual(mockProfiles[0]);
- expect(profilesModule.loadProfiles).toHaveBeenCalledWith(props);
- expect(profilesModule.loadProfiles).toHaveBeenCalledTimes(1);
- });
- });
+ jest
+ .spyOn(profilesModule, 'loadProfiles')
+ .mockResolvedValue({profiles: mockProfiles, count: 3})
- describe('when an error occurs', () => {
- it('should not return profile information', async () => {
- jest.spyOn(profilesModule, 'loadProfiles').mockRejectedValue(null);
-
- const props = {
- limit: 2,
- orderBy: "last_online_time" as const,
- };
- const mockReq = {} as any;
- const results: any = await profilesModule.getProfiles(props, mockReq, mockReq);
+ const results: any = await profilesModule.getProfiles(props, mockReq, mockReq)
- expect(results.status).toEqual('fail');
- expect(results.profiles).toEqual([]);
- expect(profilesModule.loadProfiles).toHaveBeenCalledWith(props);
- expect(profilesModule.loadProfiles).toHaveBeenCalledTimes(1);
- });
- });
-});
+ expect(results.status).toEqual('success')
+ expect(results.profiles).toEqual(mockProfiles)
+ expect(results.profiles[0]).toEqual(mockProfiles[0])
+ expect(profilesModule.loadProfiles).toHaveBeenCalledWith(props)
+ expect(profilesModule.loadProfiles).toHaveBeenCalledTimes(1)
+ })
+ })
+
+ describe('when an error occurs', () => {
+ it('should not return profile information', async () => {
+ jest.spyOn(profilesModule, 'loadProfiles').mockRejectedValue(null)
+
+ const props = {
+ limit: 2,
+ orderBy: 'last_online_time' as const,
+ }
+ const mockReq = {} as any
+ const results: any = await profilesModule.getProfiles(props, mockReq, mockReq)
+
+ expect(results.status).toEqual('fail')
+ expect(results.profiles).toEqual([])
+ expect(profilesModule.loadProfiles).toHaveBeenCalledWith(props)
+ expect(profilesModule.loadProfiles).toHaveBeenCalledTimes(1)
+ })
+ })
+})
describe('loadProfiles', () => {
- let mockPg: any;
- beforeEach(() => {
- jest.clearAllMocks();
- mockPg = {
- map: jest.fn(),
- one: jest.fn()
- };
-
- jest.spyOn(supabaseInit, 'createSupabaseDirectClient')
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg: any
+ beforeEach(() => {
+ jest.clearAllMocks()
+ mockPg = {
+ map: jest.fn(),
+ one: jest.fn(),
+ }
- describe('when given valid input', () => {
- describe('should call pg.map with an SQL query', () => {
- it('successfully', async () => {
- const mockProps = {
- limit: 10,
- name: 'John',
- is_smoker: true,
- };
+ jest.spyOn(supabaseInit, 'createSupabaseDirectClient').mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (mockPg.map as jest.Mock).mockResolvedValue([]);
- (mockPg.one as jest.Mock).mockResolvedValue(1);
- jest.spyOn(sqlBuilder, 'renderSql');
- jest.spyOn(sqlBuilder, 'select');
- jest.spyOn(sqlBuilder, 'from');
- jest.spyOn(sqlBuilder, 'where');
- jest.spyOn(sqlBuilder, 'join');
+ describe('when given valid input', () => {
+ describe('should call pg.map with an SQL query', () => {
+ it('successfully', async () => {
+ const mockProps = {
+ limit: 10,
+ name: 'John',
+ is_smoker: true,
+ }
- await profilesModule.loadProfiles(mockProps);
+ ;(mockPg.map as jest.Mock).mockResolvedValue([])
+ ;(mockPg.one as jest.Mock).mockResolvedValue(1)
+ jest.spyOn(sqlBuilder, 'renderSql')
+ jest.spyOn(sqlBuilder, 'select')
+ jest.spyOn(sqlBuilder, 'from')
+ jest.spyOn(sqlBuilder, 'where')
+ jest.spyOn(sqlBuilder, 'join')
- const [query, values, cb] = mockPg.map.mock.calls[0];
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain('select');
- expect(query).toContain('from profiles');
- expect(query).toContain('where');
- expect(query).toContain('limit 10');
- expect(query).toContain(`John`);
- expect(query).toContain(`is_smoker`);
- expect(query).not.toContain(`gender`);
- expect(query).not.toContain(`education_level`);
- expect(query).not.toContain(`pref_gender`);
- expect(query).not.toContain(`age`);
- expect(query).not.toContain(`drinks_per_month`);
- expect(query).not.toContain(`pref_relation_styles`);
- expect(query).not.toContain(`pref_romantic_styles`);
- expect(query).not.toContain(`diet`);
- expect(query).not.toContain(`political_beliefs`);
- expect(query).not.toContain(`religion`);
- expect(query).not.toContain(`has_kids`);
- expect(sqlBuilder.renderSql).toBeCalledTimes(3);
- expect(sqlBuilder.select).toBeCalledTimes(3);
- expect(sqlBuilder.from).toBeCalledTimes(2);
- expect(sqlBuilder.where).toBeCalledTimes(8);
- expect(sqlBuilder.join).toBeCalledTimes(1);
- });
-
- it('that contains a gender filter', async () => {
- await profilesModule.loadProfiles({
- genders: ['Electrical_gender'],
- });
+ await profilesModule.loadProfiles(mockProps)
- const [query, values, cb] = mockPg.map.mock.calls[0]
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain(`gender`);
- expect(query).toContain(`Electrical_gender`);
- });
-
- it('that contains a education level filter', async () => {
- await profilesModule.loadProfiles({
- education_levels: ['High School'],
- });
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
- const [query, values, cb] = mockPg.map.mock.calls[0]
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain(`education_level`);
- expect(query).toContain(`High School`);
- });
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain('select')
+ expect(query).toContain('from profiles')
+ expect(query).toContain('where')
+ expect(query).toContain('limit 10')
+ expect(query).toContain(`John`)
+ expect(query).toContain(`is_smoker`)
+ expect(query).not.toContain(`gender`)
+ expect(query).not.toContain(`education_level`)
+ expect(query).not.toContain(`pref_gender`)
+ expect(query).not.toContain(`age`)
+ expect(query).not.toContain(`drinks_per_month`)
+ expect(query).not.toContain(`pref_relation_styles`)
+ expect(query).not.toContain(`pref_romantic_styles`)
+ expect(query).not.toContain(`diet`)
+ expect(query).not.toContain(`political_beliefs`)
+ expect(query).not.toContain(`religion`)
+ expect(query).not.toContain(`has_kids`)
+ expect(sqlBuilder.renderSql).toBeCalledTimes(3)
+ expect(sqlBuilder.select).toBeCalledTimes(3)
+ expect(sqlBuilder.from).toBeCalledTimes(2)
+ expect(sqlBuilder.where).toBeCalledTimes(8)
+ expect(sqlBuilder.join).toBeCalledTimes(1)
+ })
- it('that contains a prefer gender filter', async () => {
- await profilesModule.loadProfiles({
- pref_gender: ['female'],
- });
+ it('that contains a gender filter', async () => {
+ await profilesModule.loadProfiles({
+ genders: ['Electrical_gender'],
+ })
- const [query, values, cb] = mockPg.map.mock.calls[0]
- console.log(query);
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain(`pref_gender`);
- expect(query).toContain(`female`);
- });
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
- it('that contains a minimum age filter', async () => {
- await profilesModule.loadProfiles({
- pref_age_min: 20,
- });
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain(`gender`)
+ expect(query).toContain(`Electrical_gender`)
+ })
- const [query, values, cb] = mockPg.map.mock.calls[0]
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain(`age`);
- expect(query).toContain(`>= 20`);
- });
+ it('that contains a education level filter', async () => {
+ await profilesModule.loadProfiles({
+ education_levels: ['High School'],
+ })
- it('that contains a maximum age filter', async () => {
- await profilesModule.loadProfiles({
- pref_age_max: 40,
- });
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
- const [query, values, cb] = mockPg.map.mock.calls[0]
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain(`age`);
- expect(query).toContain(`<= 40`);
- });
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain(`education_level`)
+ expect(query).toContain(`High School`)
+ })
- it('that contains a minimum drinks per month filter', async () => {
- await profilesModule.loadProfiles({
- drinks_min: 4,
- });
+ it('that contains a prefer gender filter', async () => {
+ await profilesModule.loadProfiles({
+ pref_gender: ['female'],
+ })
- const [query, values, cb] = mockPg.map.mock.calls[0]
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain(`drinks_per_month`);
- expect(query).toContain('4');
- });
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
+ console.log(query)
- it('that contains a maximum drinks per month filter', async () => {
- await profilesModule.loadProfiles({
- drinks_max: 20,
- });
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain(`pref_gender`)
+ expect(query).toContain(`female`)
+ })
- const [query, values, cb] = mockPg.map.mock.calls[0]
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain(`drinks_per_month`);
- expect(query).toContain('20');
- });
+ it('that contains a minimum age filter', async () => {
+ await profilesModule.loadProfiles({
+ pref_age_min: 20,
+ })
- it('that contains a relationship style filter', async () => {
- await profilesModule.loadProfiles({
- pref_relation_styles: ['Chill and relaxing'],
- });
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
- const [query, values, cb] = mockPg.map.mock.calls[0]
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain(`pref_relation_styles`);
- expect(query).toContain('Chill and relaxing');
- });
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain(`age`)
+ expect(query).toContain(`>= 20`)
+ })
- it('that contains a romantic style filter', async () => {
- await profilesModule.loadProfiles({
- pref_romantic_styles: ['Sexy'],
- });
+ it('that contains a maximum age filter', async () => {
+ await profilesModule.loadProfiles({
+ pref_age_max: 40,
+ })
- const [query, values, cb] = mockPg.map.mock.calls[0]
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain(`pref_romantic_styles`);
- expect(query).toContain('Sexy');
- });
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
- it('that contains a diet filter', async () => {
- await profilesModule.loadProfiles({
- diet: ['Glutton'],
- });
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain(`age`)
+ expect(query).toContain(`<= 40`)
+ })
- const [query, values, cb] = mockPg.map.mock.calls[0]
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain(`diet`);
- expect(query).toContain('Glutton');
- });
+ it('that contains a minimum drinks per month filter', async () => {
+ await profilesModule.loadProfiles({
+ drinks_min: 4,
+ })
- it('that contains a political beliefs filter', async () => {
- await profilesModule.loadProfiles({
- political_beliefs: ['For the people'],
- });
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
- const [query, values, cb] = mockPg.map.mock.calls[0]
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain(`political_beliefs`);
- expect(query).toContain('For the people');
- });
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain(`drinks_per_month`)
+ expect(query).toContain('4')
+ })
- it('that contains a religion filter', async () => {
- await profilesModule.loadProfiles({
- religion: ['The blood god'],
- });
+ it('that contains a maximum drinks per month filter', async () => {
+ await profilesModule.loadProfiles({
+ drinks_max: 20,
+ })
- const [query, values, cb] = mockPg.map.mock.calls[0]
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain(`religion`);
- expect(query).toContain('The blood god');
- });
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
- it('that contains a has kids filter', async () => {
- await profilesModule.loadProfiles({
- has_kids: 3,
- });
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain(`drinks_per_month`)
+ expect(query).toContain('20')
+ })
- const [query, values, cb] = mockPg.map.mock.calls[0]
-
- expect(mockPg.map.mock.calls).toHaveLength(1)
- expect(query).toContain(`has_kids`);
- expect(query).toContain('> 0');
- });
+ it('that contains a relationship style filter', async () => {
+ await profilesModule.loadProfiles({
+ pref_relation_styles: ['Chill and relaxing'],
+ })
- it('should return profiles from the database', async () => {
- const mockProfiles = [
- {
- diet: ['Jonathon Hammon'],
- is_smoker: true,
- has_kids: 0
- },
- {
- diet: ['Joseph Hammon'],
- is_smoker: false,
- has_kids: 1
- },
- {
- diet: ['Jolene Hammon'],
- is_smoker: true,
- has_kids: 2,
- }
- ] as Profile [];
- const props = {} as any;
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
- (mockPg.map as jest.Mock).mockResolvedValue(mockProfiles);
- (mockPg.one as jest.Mock).mockResolvedValue(1);
-
- const results = await profilesModule.loadProfiles(props);
-
- expect(results).toEqual({profiles: mockProfiles, count: 1});
- });
- });
- });
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain(`pref_relation_styles`)
+ expect(query).toContain('Chill and relaxing')
+ })
- describe('when an error occurs', () => {
- it('throw if there is no compatability', async () => {
- const props = {
- orderBy: 'compatibility_score'
- }
+ it('that contains a romantic style filter', async () => {
+ await profilesModule.loadProfiles({
+ pref_romantic_styles: ['Sexy'],
+ })
- expect(profilesModule.loadProfiles(props))
- .rejects
- .toThrowError('Incompatible with user ID')
- });
- });
-});
\ No newline at end of file
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
+
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain(`pref_romantic_styles`)
+ expect(query).toContain('Sexy')
+ })
+
+ it('that contains a diet filter', async () => {
+ await profilesModule.loadProfiles({
+ diet: ['Glutton'],
+ })
+
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
+
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain(`diet`)
+ expect(query).toContain('Glutton')
+ })
+
+ it('that contains a political beliefs filter', async () => {
+ await profilesModule.loadProfiles({
+ political_beliefs: ['For the people'],
+ })
+
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
+
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain(`political_beliefs`)
+ expect(query).toContain('For the people')
+ })
+
+ it('that contains a religion filter', async () => {
+ await profilesModule.loadProfiles({
+ religion: ['The blood god'],
+ })
+
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
+
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain(`religion`)
+ expect(query).toContain('The blood god')
+ })
+
+ it('that contains a has kids filter', async () => {
+ await profilesModule.loadProfiles({
+ has_kids: 3,
+ })
+
+ const [query, _values, _cb] = mockPg.map.mock.calls[0]
+
+ expect(mockPg.map.mock.calls).toHaveLength(1)
+ expect(query).toContain(`has_kids`)
+ expect(query).toContain('> 0')
+ })
+
+ it('should return profiles from the database', async () => {
+ const mockProfiles = [
+ {
+ diet: ['Jonathon Hammon'],
+ is_smoker: true,
+ has_kids: 0,
+ },
+ {
+ diet: ['Joseph Hammon'],
+ is_smoker: false,
+ has_kids: 1,
+ },
+ {
+ diet: ['Jolene Hammon'],
+ is_smoker: true,
+ has_kids: 2,
+ },
+ ] as Profile[]
+ const props = {} as any
+
+ ;(mockPg.map as jest.Mock).mockResolvedValue(mockProfiles)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(1)
+
+ const results = await profilesModule.loadProfiles(props)
+
+ expect(results).toEqual({profiles: mockProfiles, count: 1})
+ })
+ })
+ })
+
+ describe('when an error occurs', () => {
+ it('throw if there is no compatability', async () => {
+ const props = {
+ orderBy: 'compatibility_score',
+ }
+
+ expect(profilesModule.loadProfiles(props)).rejects.toThrowError('Incompatible with user ID')
+ })
+ })
+})
diff --git a/backend/api/tests/unit/get-supabase-token.unit.test.ts b/backend/api/tests/unit/get-supabase-token.unit.test.ts
index a695ba4c..0a294b71 100644
--- a/backend/api/tests/unit/get-supabase-token.unit.test.ts
+++ b/backend/api/tests/unit/get-supabase-token.unit.test.ts
@@ -1,49 +1,40 @@
-jest.mock('jsonwebtoken');
-
+jest.mock('jsonwebtoken')
describe.skip('getSupabaseToken', () => {
- // const originalSupabaseJwtSecret = process.env.SUPABASE_JWT_SECRET
- // const originalInstanceId = constants.ENV_CONFIG.supabaseInstanceId
- // const originalProjectId = constants.ENV_CONFIG.firebaseConfig.projectId
-
- // describe('should', () => {
- // beforeEach(() => {
- // jest.resetAllMocks();
-
- // process.env.SUPABASE_JWT_SECRET = 'test-jwt-secret-123';
- // constants.ENV_CONFIG.supabaseInstanceId = 'test-instance-id';
- // constants.ENV_CONFIG.firebaseConfig.projectId = 'test-project-id';
-
- // (jsonWebtokenModules.sign as jest.Mock).mockReturnValue('fake-jwt-token-abc123');
- // });
-
- // afterEach(() => {
- // if (originalSupabaseJwtSecret === undefined) {
- // delete process.env.SUPABASE_JWT_SECRET;
- // } else {
- // process.env.SUPABASE_JWT_SECRET = originalSupabaseJwtSecret;
- // }
- // constants.ENV_CONFIG.supabaseInstanceId = originalInstanceId;
- // constants.ENV_CONFIG.firebaseConfig.projectId = originalProjectId;
-
- // jest.restoreAllMocks();
- // });
-
- // it('successfully generate a JTW token with correct parameters', async () => {
- // const mockParams = {} as any;
- // const mockAuth = {uid: '321'} as AuthedUser;
- // const result = await getSupabaseToken(mockParams, mockAuth, mockParams)
-
- // expect(result).toEqual({
- // jwt: 'fake-jwt-token-abc123'
- // })
- // })
- // });
-});
+ // const originalSupabaseJwtSecret = process.env.SUPABASE_JWT_SECRET
+ // const originalInstanceId = constants.ENV_CONFIG.supabaseInstanceId
+ // const originalProjectId = constants.ENV_CONFIG.firebaseConfig.projectId
+ // describe('should', () => {
+ // beforeEach(() => {
+ // jest.resetAllMocks();
+ // process.env.SUPABASE_JWT_SECRET = 'test-jwt-secret-123';
+ // constants.ENV_CONFIG.supabaseInstanceId = 'test-instance-id';
+ // constants.ENV_CONFIG.firebaseConfig.projectId = 'test-project-id';
+ // (jsonWebtokenModules.sign as jest.Mock).mockReturnValue('fake-jwt-token-abc123');
+ // });
+ // afterEach(() => {
+ // if (originalSupabaseJwtSecret === undefined) {
+ // delete process.env.SUPABASE_JWT_SECRET;
+ // } else {
+ // process.env.SUPABASE_JWT_SECRET = originalSupabaseJwtSecret;
+ // }
+ // constants.ENV_CONFIG.supabaseInstanceId = originalInstanceId;
+ // constants.ENV_CONFIG.firebaseConfig.projectId = originalProjectId;
+ // jest.restoreAllMocks();
+ // });
+ // it('successfully generate a JTW token with correct parameters', async () => {
+ // const mockParams = {} as any;
+ // const mockAuth = {uid: '321'} as AuthedUser;
+ // const result = await getSupabaseToken(mockParams, mockAuth, mockParams)
+ // expect(result).toEqual({
+ // jwt: 'fake-jwt-token-abc123'
+ // })
+ // })
+ // });
+})
describe('getCompatibleProfiles', () => {
- it('skip', async () => {
- console.log('This needs tests');
-
- })
-})
\ No newline at end of file
+ it('skip', async () => {
+ console.log('This needs tests')
+ })
+})
diff --git a/backend/api/tests/unit/get-users.unit.test.ts b/backend/api/tests/unit/get-users.unit.test.ts
index 112095e4..0970053a 100644
--- a/backend/api/tests/unit/get-users.unit.test.ts
+++ b/backend/api/tests/unit/get-users.unit.test.ts
@@ -1,91 +1,85 @@
-import {sqlMatch} from "common/test-utils";
-import {getUser} from "api/get-user";
-import * as supabaseInit from "shared/supabase/init";
-import {toUserAPIResponse} from "common/api/user-types";
+import {getUser} from 'api/get-user'
+import {toUserAPIResponse} from 'common/api/user-types'
+import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
-jest.mock("shared/supabase/init");
-jest.mock("common/supabase/users");
-jest.mock("common/api/user-types");
+jest.mock('shared/supabase/init')
+jest.mock('common/supabase/users')
+jest.mock('common/api/user-types')
-describe('getUser', () =>{
- let mockPg: any;
+describe('getUser', () => {
+ let mockPg: any
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- };
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ describe('and fetching by id', () => {
+ it('should fetch user successfully by id', async () => {
+ const mockProps = {id: 'mockId'}
+ const mockUser = {} as any
- describe('when given valid input', () => {
- describe('and fetching by id', () => {
- it('should fetch user successfully by id', async () => {
- const mockProps = {id: "mockId"};
- const mockUser = {} as any;
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockUser)
+ ;(toUserAPIResponse as jest.Mock).mockReturnValue('mockApiResponse')
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockUser);
- (toUserAPIResponse as jest.Mock).mockReturnValue('mockApiResponse');
-
- const result = await getUser(mockProps);
+ const result = await getUser(mockProps)
- expect(result).toBe('mockApiResponse');
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select * from users'),
- [mockProps.id],
- expect.any(Function)
- );
- expect(toUserAPIResponse).toBeCalledTimes(1);
- expect(toUserAPIResponse).toBeCalledWith(mockUser);
- });
- });
+ expect(result).toBe('mockApiResponse')
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(
+ sqlMatch('select * from users'),
+ [mockProps.id],
+ expect.any(Function),
+ )
+ expect(toUserAPIResponse).toBeCalledTimes(1)
+ expect(toUserAPIResponse).toBeCalledWith(mockUser)
+ })
+ })
- describe('when fetching by username', () => {
- it('should fetch user successfully by username', async () => {
- const mockProps = {username: "mockUsername"};
- const mockUser = {} as any;
+ describe('when fetching by username', () => {
+ it('should fetch user successfully by username', async () => {
+ const mockProps = {username: 'mockUsername'}
+ const mockUser = {} as any
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockUser);
-
- await getUser(mockProps)
-
- expect(mockPg.oneOrNone).toHaveBeenCalledWith(
- sqlMatch('where username = $1'),
- [mockProps.username],
- expect.any(Function)
- );
- });
- });
- });
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockUser)
- describe('when an error occurs', () => {
- describe('and fetching by id', () => {
- it('should throw when user is not found by id', async () => {
- const mockProps = {id: "mockId"};
+ await getUser(mockProps)
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(false);
-
- expect(getUser(mockProps))
- .rejects
- .toThrow('User not found');
- });
- });
- describe('when fetching by username', () => {
- it('should throw when user is not found by id', async () => {
- const mockProps = {username: "mockUsername"};
+ expect(mockPg.oneOrNone).toHaveBeenCalledWith(
+ sqlMatch('where username = $1'),
+ [mockProps.username],
+ expect.any(Function),
+ )
+ })
+ })
+ })
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(false);
-
- expect(getUser(mockProps))
- .rejects
- .toThrow('User not found');
- });
- });
- });
-});
\ No newline at end of file
+ describe('when an error occurs', () => {
+ describe('and fetching by id', () => {
+ it('should throw when user is not found by id', async () => {
+ const mockProps = {id: 'mockId'}
+
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(false)
+
+ expect(getUser(mockProps)).rejects.toThrow('User not found')
+ })
+ })
+ describe('when fetching by username', () => {
+ it('should throw when user is not found by id', async () => {
+ const mockProps = {username: 'mockUsername'}
+
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(false)
+
+ expect(getUser(mockProps)).rejects.toThrow('User not found')
+ })
+ })
+ })
+})
diff --git a/backend/api/tests/unit/has-free-like.unit.test.ts b/backend/api/tests/unit/has-free-like.unit.test.ts
index 88018570..8813044a 100644
--- a/backend/api/tests/unit/has-free-like.unit.test.ts
+++ b/backend/api/tests/unit/has-free-like.unit.test.ts
@@ -1,57 +1,54 @@
-jest.mock('shared/supabase/init');
+jest.mock('shared/supabase/init')
-import * as freeLikeModule from "api/has-free-like";
-import { AuthedUser } from "api/helpers/endpoint";
-import * as supabaseInit from "shared/supabase/init";
+import * as freeLikeModule from 'api/has-free-like'
+import {AuthedUser} from 'api/helpers/endpoint'
+import * as supabaseInit from 'shared/supabase/init'
describe('hasFreeLike', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should return if the user has a free like', async () => {
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockProps = {} as any
- describe('when given valid input', () => {
- it('should return if the user has a free like', async () => {
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockProps = {} as any;
+ jest.spyOn(freeLikeModule, 'getHasFreeLike')
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(false)
- jest.spyOn( freeLikeModule, 'getHasFreeLike');
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(false);
+ const result: any = await freeLikeModule.hasFreeLike(mockProps, mockAuth, mockReq)
- const result: any = await freeLikeModule.hasFreeLike(mockProps, mockAuth, mockReq);
+ expect(result.status).toBe('success')
+ expect(result.hasFreeLike).toBeTruthy()
+ expect(freeLikeModule.getHasFreeLike).toBeCalledTimes(1)
+ expect(freeLikeModule.getHasFreeLike).toBeCalledWith(mockAuth.uid)
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(expect.stringContaining('from profile_likes'), [
+ mockAuth.uid,
+ ])
+ })
- expect(result.status).toBe('success');
- expect(result.hasFreeLike).toBeTruthy();
- expect(freeLikeModule.getHasFreeLike).toBeCalledTimes(1);
- expect(freeLikeModule.getHasFreeLike).toBeCalledWith(mockAuth.uid);
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- expect.stringContaining('from profile_likes'),
- [mockAuth.uid]
- );
- });
+ it('should return if the user does not have a free like', async () => {
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockProps = {} as any
- it('should return if the user does not have a free like', async () => {
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockProps = {} as any;
+ jest.spyOn(freeLikeModule, 'getHasFreeLike')
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(true)
- jest.spyOn( freeLikeModule, 'getHasFreeLike');
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(true);
+ const result: any = await freeLikeModule.hasFreeLike(mockProps, mockAuth, mockReq)
- const result: any = await freeLikeModule.hasFreeLike(mockProps, mockAuth, mockReq);
-
- expect(result.hasFreeLike).toBeFalsy();
- });
- });
-});
+ expect(result.hasFreeLike).toBeFalsy()
+ })
+ })
+})
diff --git a/backend/api/tests/unit/health-unit.test.ts b/backend/api/tests/unit/health-unit.test.ts
index cc0fe452..1fd0a9d7 100644
--- a/backend/api/tests/unit/health-unit.test.ts
+++ b/backend/api/tests/unit/health-unit.test.ts
@@ -1,16 +1,16 @@
-import { health } from "api/health";
-import { AuthedUser } from "api/helpers/endpoint";
+import {health} from 'api/health'
+import {AuthedUser} from 'api/helpers/endpoint'
describe('health', () => {
- describe('when given valid input', () => {
- it('should return the servers status(Health)', async () => {
- const mockProps = {} as any;
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ describe('when given valid input', () => {
+ it('should return the servers status(Health)', async () => {
+ const mockProps = {} as any
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- const result: any = await health(mockProps, mockAuth, mockReq);
- expect(result.message).toBe('Server is working.');
- expect(result.uid).toBe(mockAuth.uid);
- });
- });
-});
\ No newline at end of file
+ const result: any = await health(mockProps, mockAuth, mockReq)
+ expect(result.message).toBe('Server is working.')
+ expect(result.uid).toBe(mockAuth.uid)
+ })
+ })
+})
diff --git a/backend/api/tests/unit/hide-comment.unit.test.ts b/backend/api/tests/unit/hide-comment.unit.test.ts
index 76157486..e4fd096e 100644
--- a/backend/api/tests/unit/hide-comment.unit.test.ts
+++ b/backend/api/tests/unit/hide-comment.unit.test.ts
@@ -1,166 +1,162 @@
-import {sqlMatch} from "common/test-utils";
-import {hideComment} from "api/hide-comment";
-import * as supabaseInit from "shared/supabase/init";
-import * as envConsts from "common/envs/constants";
-import {convertComment} from "common/supabase/comment";
-import * as websocketHelpers from "shared/websockets/helpers";
-import {AuthedUser} from "api/helpers/endpoint";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {hideComment} from 'api/hide-comment'
+import * as envConsts from 'common/envs/constants'
+import {convertComment} from 'common/supabase/comment'
+import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
+import * as websocketHelpers from 'shared/websockets/helpers'
-jest.mock('shared/supabase/init');
-jest.mock('common/supabase/comment');
-jest.mock('shared/websockets/helpers');
+jest.mock('shared/supabase/init')
+jest.mock('common/supabase/comment')
+jest.mock('shared/websockets/helpers')
describe('hideComment', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- none: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should successfully hide the comment if the user is an admin', async () => {
+ const mockProps = {
+ commentId: 'mockCommentId',
+ hide: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockComment = {
+ content: {mockContent: 'mockContentValue'},
+ created_time: 'mockCreatedTime',
+ hidden: false,
+ id: 123,
+ on_user_id: '4321',
+ reply_to_comment_id: null,
+ user_avatar_url: 'mockAvatarUrl',
+ user_id: '4321',
+ user_name: 'mockUserName',
+ user_username: 'mockUserUsername',
+ }
+ const mockConvertedComment = 'mockConvertedCommentValue'
- describe('when given valid input', () => {
- it('should successfully hide the comment if the user is an admin', async () => {
- const mockProps = {
- commentId: "mockCommentId",
- hide: true
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockComment = {
- content: { "mockContent": "mockContentValue" },
- created_time: "mockCreatedTime",
- hidden: false,
- id: 123,
- on_user_id: "4321",
- reply_to_comment_id: null,
- user_avatar_url: "mockAvatarUrl",
- user_id: "4321",
- user_name: "mockUserName",
- user_username: "mockUserUsername",
- };
- const mockConvertedComment = "mockConvertedCommentValue";
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockComment)
+ jest.spyOn(envConsts, 'isAdminId').mockReturnValue(true)
+ ;(convertComment as jest.Mock).mockReturnValue(mockConvertedComment)
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockComment);
- jest.spyOn(envConsts, 'isAdminId').mockReturnValue(true);
- (convertComment as jest.Mock).mockReturnValue(mockConvertedComment);
+ await hideComment(mockProps, mockAuth, mockReq)
- await hideComment(mockProps, mockAuth, mockReq);
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(
+ sqlMatch('select * from profile_comments where id = $1'),
+ [mockProps.commentId],
+ )
+ expect(envConsts.isAdminId).toBeCalledTimes(1)
+ expect(envConsts.isAdminId).toBeCalledWith(mockAuth.uid)
+ expect(convertComment).toBeCalledTimes(1)
+ expect(convertComment).toBeCalledWith(mockComment)
+ expect(websocketHelpers.broadcastUpdatedComment).toBeCalledTimes(1)
+ expect(websocketHelpers.broadcastUpdatedComment).toBeCalledWith(mockConvertedComment)
+ })
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select * from profile_comments where id = $1'),
- [mockProps.commentId]
- );
- expect(envConsts.isAdminId).toBeCalledTimes(1);
- expect(envConsts.isAdminId).toBeCalledWith(mockAuth.uid);
- expect(convertComment).toBeCalledTimes(1);
- expect(convertComment).toBeCalledWith(mockComment);
- expect(websocketHelpers.broadcastUpdatedComment).toBeCalledTimes(1);
- expect(websocketHelpers.broadcastUpdatedComment).toBeCalledWith(mockConvertedComment);
- });
+ it('should successfully hide the comment if the user is the one who made the comment', async () => {
+ const mockProps = {
+ commentId: 'mockCommentId',
+ hide: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockComment = {
+ content: {mockContent: 'mockContentValue'},
+ created_time: 'mockCreatedTime',
+ hidden: false,
+ id: 123,
+ on_user_id: '4321',
+ reply_to_comment_id: null,
+ user_avatar_url: 'mockAvatarUrl',
+ user_id: '321',
+ user_name: 'mockUserName',
+ user_username: 'mockUserUsername',
+ }
- it('should successfully hide the comment if the user is the one who made the comment', async () => {
- const mockProps = {
- commentId: "mockCommentId",
- hide: true
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockComment = {
- content: { "mockContent": "mockContentValue" },
- created_time: "mockCreatedTime",
- hidden: false,
- id: 123,
- on_user_id: "4321",
- reply_to_comment_id: null,
- user_avatar_url: "mockAvatarUrl",
- user_id: "321",
- user_name: "mockUserName",
- user_username: "mockUserUsername",
- };
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockComment)
+ jest.spyOn(envConsts, 'isAdminId').mockReturnValue(false)
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockComment);
- jest.spyOn(envConsts, 'isAdminId').mockReturnValue(false);
+ await hideComment(mockProps, mockAuth, mockReq)
+ })
- await hideComment(mockProps, mockAuth, mockReq);
- });
+ it('should successfully hide the comment if the user is the one who is being commented on', async () => {
+ const mockProps = {
+ commentId: 'mockCommentId',
+ hide: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockComment = {
+ content: {mockContent: 'mockContentValue'},
+ created_time: 'mockCreatedTime',
+ hidden: false,
+ id: 123,
+ on_user_id: '321',
+ reply_to_comment_id: null,
+ user_avatar_url: 'mockAvatarUrl',
+ user_id: '4321',
+ user_name: 'mockUserName',
+ user_username: 'mockUserUsername',
+ }
- it('should successfully hide the comment if the user is the one who is being commented on', async () => {
- const mockProps = {
- commentId: "mockCommentId",
- hide: true
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockComment = {
- content: { "mockContent": "mockContentValue" },
- created_time: "mockCreatedTime",
- hidden: false,
- id: 123,
- on_user_id: "321",
- reply_to_comment_id: null,
- user_avatar_url: "mockAvatarUrl",
- user_id: "4321",
- user_name: "mockUserName",
- user_username: "mockUserUsername",
- };
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockComment)
+ jest.spyOn(envConsts, 'isAdminId').mockReturnValue(false)
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockComment);
- jest.spyOn(envConsts, 'isAdminId').mockReturnValue(false);
+ await hideComment(mockProps, mockAuth, mockReq)
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if the comment was not found', async () => {
+ const mockProps = {
+ commentId: 'mockCommentId',
+ hide: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- await hideComment(mockProps, mockAuth, mockReq);
- });
- });
- describe('when an error occurs', () => {
- it('should throw if the comment was not found', async () => {
- const mockProps = {
- commentId: "mockCommentId",
- hide: true
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(false)
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(false);
+ expect(hideComment(mockProps, mockAuth, mockReq)).rejects.toThrow('Comment not found')
+ })
- expect(hideComment(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Comment not found');
- });
-
- it('should throw if the user is not an admin, the comments author or the one being commented on', async () => {
- const mockProps = {
- commentId: "mockCommentId",
- hide: true
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockComment = {
- content: { "mockContent": "mockContentValue" },
- created_time: "mockCreatedTime",
- hidden: false,
- id: 123,
- on_user_id: "4321",
- reply_to_comment_id: null,
- user_avatar_url: "mockAvatarUrl",
- user_id: "4321",
- user_name: "mockUserName",
- user_username: "mockUserUsername",
- };
+ it('should throw if the user is not an admin, the comments author or the one being commented on', async () => {
+ const mockProps = {
+ commentId: 'mockCommentId',
+ hide: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockComment = {
+ content: {mockContent: 'mockContentValue'},
+ created_time: 'mockCreatedTime',
+ hidden: false,
+ id: 123,
+ on_user_id: '4321',
+ reply_to_comment_id: null,
+ user_avatar_url: 'mockAvatarUrl',
+ user_id: '4321',
+ user_name: 'mockUserName',
+ user_username: 'mockUserUsername',
+ }
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockComment);
- jest.spyOn(envConsts, 'isAdminId').mockReturnValue(false);
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockComment)
+ jest.spyOn(envConsts, 'isAdminId').mockReturnValue(false)
- expect(hideComment(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('You are not allowed to hide this comment');
- });
- });
-});
\ No newline at end of file
+ expect(hideComment(mockProps, mockAuth, mockReq)).rejects.toThrow(
+ 'You are not allowed to hide this comment',
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/leave-private-user-message-channel.unit.test.ts b/backend/api/tests/unit/leave-private-user-message-channel.unit.test.ts
index 2df647f4..87287574 100644
--- a/backend/api/tests/unit/leave-private-user-message-channel.unit.test.ts
+++ b/backend/api/tests/unit/leave-private-user-message-channel.unit.test.ts
@@ -1,95 +1,93 @@
-jest.mock('shared/supabase/init');
-jest.mock('shared/utils');
-jest.mock('api/helpers/private-messages');
+jest.mock('shared/supabase/init')
+jest.mock('shared/utils')
+jest.mock('api/helpers/private-messages')
-import {leavePrivateUserMessageChannel} from "api/leave-private-user-message-channel";
-import * as supabaseInit from "shared/supabase/init";
-import * as sharedUtils from "shared/utils";
-import * as messageHelpers from "api/helpers/private-messages";
-import {AuthedUser} from "api/helpers/endpoint";
+import {AuthedUser} from 'api/helpers/endpoint'
+import * as messageHelpers from 'api/helpers/private-messages'
+import {leavePrivateUserMessageChannel} from 'api/leave-private-user-message-channel'
import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
+import * as sharedUtils from 'shared/utils'
describe('leavePrivateUserMessageChannel', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- none: jest.fn()
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should leave a private message channel', async () => {
- const mockProps = { channelId: 123 };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockUser = { name: "mockName" };
- const mockLeaveChatContent = "mockLeaveChatContentValue";
+ describe('when given valid input', () => {
+ it('should leave a private message channel', async () => {
+ const mockProps = {channelId: 123}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockUser = {name: 'mockName'}
+ const mockLeaveChatContent = 'mockLeaveChatContentValue'
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(true);
- (messageHelpers.leaveChatContent as jest.Mock).mockReturnValue(mockLeaveChatContent);
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(true)
+ ;(messageHelpers.leaveChatContent as jest.Mock).mockReturnValue(mockLeaveChatContent)
- const results = await leavePrivateUserMessageChannel(mockProps, mockAuth, mockReq);
+ const results = await leavePrivateUserMessageChannel(mockProps, mockAuth, mockReq)
- expect(results.status).toBe('success');
- expect(results.channelId).toBe(mockProps.channelId);
- expect(sharedUtils.getUser).toBeCalledTimes(1);
- expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid);
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select status from private_user_message_channel_members'),
- [mockProps.channelId, mockAuth.uid]
- );
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('update private_user_message_channel_members'),
- [mockProps.channelId, mockAuth.uid]
- );
- expect(messageHelpers.leaveChatContent).toBeCalledTimes(1);
- expect(messageHelpers.leaveChatContent).toBeCalledWith(mockUser.name);
- expect(messageHelpers.insertPrivateMessage).toBeCalledTimes(1);
- expect(messageHelpers.insertPrivateMessage).toBeCalledWith(
- mockLeaveChatContent,
- mockProps.channelId,
- mockAuth.uid,
- 'system_status',
- expect.any(Object)
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if the account was not found', async () => {
- const mockProps = { channelId: 123 };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ expect(results.status).toBe('success')
+ expect(results.channelId).toBe(mockProps.channelId)
+ expect(sharedUtils.getUser).toBeCalledTimes(1)
+ expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid)
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(
+ sqlMatch('select status from private_user_message_channel_members'),
+ [mockProps.channelId, mockAuth.uid],
+ )
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(sqlMatch('update private_user_message_channel_members'), [
+ mockProps.channelId,
+ mockAuth.uid,
+ ])
+ expect(messageHelpers.leaveChatContent).toBeCalledTimes(1)
+ expect(messageHelpers.leaveChatContent).toBeCalledWith(mockUser.name)
+ expect(messageHelpers.insertPrivateMessage).toBeCalledTimes(1)
+ expect(messageHelpers.insertPrivateMessage).toBeCalledWith(
+ mockLeaveChatContent,
+ mockProps.channelId,
+ mockAuth.uid,
+ 'system_status',
+ expect.any(Object),
+ )
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if the account was not found', async () => {
+ const mockProps = {channelId: 123}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
- expect(leavePrivateUserMessageChannel(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Your account was not found');
- });
+ expect(leavePrivateUserMessageChannel(mockProps, mockAuth, mockReq)).rejects.toThrow(
+ 'Your account was not found',
+ )
+ })
- it('should throw if you are not a member', async () => {
- const mockProps = { channelId: 123 };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockUser = { name: "mockName" };
+ it('should throw if you are not a member', async () => {
+ const mockProps = {channelId: 123}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockUser = {name: 'mockName'}
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(false);
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(false)
-
- expect(leavePrivateUserMessageChannel(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('You are not authorized to post to this channel');
- });
- });
-});
\ No newline at end of file
+ expect(leavePrivateUserMessageChannel(mockProps, mockAuth, mockReq)).rejects.toThrow(
+ 'You are not authorized to post to this channel',
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/like-profile.unit.test.ts b/backend/api/tests/unit/like-profile.unit.test.ts
index ac1fe4c1..ed87e8ea 100644
--- a/backend/api/tests/unit/like-profile.unit.test.ts
+++ b/backend/api/tests/unit/like-profile.unit.test.ts
@@ -1,188 +1,180 @@
-import {sqlMatch} from "common/test-utils";
-import {likeProfile} from "api/like-profile";
-import * as supabaseInit from "shared/supabase/init";
-import * as profileNotifiction from "shared/create-profile-notification";
-import * as likeModules from "api/has-free-like";
-import {tryCatch} from "common/util/try-catch";
-import {AuthedUser} from "api/helpers/endpoint";
+import * as likeModules from 'api/has-free-like'
+import {AuthedUser} from 'api/helpers/endpoint'
+import {likeProfile} from 'api/like-profile'
+import {sqlMatch} from 'common/test-utils'
+import {tryCatch} from 'common/util/try-catch'
+import * as profileNotifiction from 'shared/create-profile-notification'
+import * as supabaseInit from 'shared/supabase/init'
-jest.mock('shared/supabase/init');
-jest.mock('shared/create-profile-notification');
-jest.mock('api/has-free-like');
-jest.mock('common/util/try-catch');
+jest.mock('shared/supabase/init')
+jest.mock('shared/create-profile-notification')
+jest.mock('api/has-free-like')
+jest.mock('common/util/try-catch')
describe('likeProfile', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- one: jest.fn(),
- none: jest.fn()
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ one: jest.fn(),
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should like the selected profile', async () => {
- const mockProps = {
- targetUserId: "mockTargetUserId",
- remove: false
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockData = {
- created_time: "mockCreatedTime",
- creator_id: "mockCreatorId",
- likeId: "mockLikeId",
- target_id: "mockTargetId"
- };
+ describe('when given valid input', () => {
+ it('should like the selected profile', async () => {
+ const mockProps = {
+ targetUserId: 'mockTargetUserId',
+ remove: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockData = {
+ created_time: 'mockCreatedTime',
+ creator_id: 'mockCreatorId',
+ likeId: 'mockLikeId',
+ target_id: 'mockTargetId',
+ }
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce({data: false})
- .mockResolvedValueOnce({data: mockData, error: null});
- (likeModules.getHasFreeLike as jest.Mock).mockResolvedValue(true);
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce({data: false})
+ .mockResolvedValueOnce({data: mockData, error: null})
+ ;(likeModules.getHasFreeLike as jest.Mock).mockResolvedValue(true)
- const result: any = await likeProfile(mockProps, mockAuth, mockReq);
+ const result: any = await likeProfile(mockProps, mockAuth, mockReq)
- expect(result.result.status).toBe('success');
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select * from profile_likes where creator_id = $1 and target_id = $2'),
- [mockAuth.uid, mockProps.targetUserId]
- );
- expect(tryCatch).toBeCalledTimes(2);
- expect(mockPg.one).toBeCalledTimes(1);
- expect(mockPg.one).toBeCalledWith(
- sqlMatch('insert into profile_likes (creator_id, target_id) values ($1, $2) returning *'),
- [mockAuth.uid, mockProps.targetUserId]
- );
+ expect(result.result.status).toBe('success')
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(
+ sqlMatch('select * from profile_likes where creator_id = $1 and target_id = $2'),
+ [mockAuth.uid, mockProps.targetUserId],
+ )
+ expect(tryCatch).toBeCalledTimes(2)
+ expect(mockPg.one).toBeCalledTimes(1)
+ expect(mockPg.one).toBeCalledWith(
+ sqlMatch('insert into profile_likes (creator_id, target_id) values ($1, $2) returning *'),
+ [mockAuth.uid, mockProps.targetUserId],
+ )
+ ;(profileNotifiction.createProfileLikeNotification as jest.Mock).mockResolvedValue(null)
- (profileNotifiction.createProfileLikeNotification as jest.Mock).mockResolvedValue(null);
+ await result.continue()
- await result.continue();
+ expect(profileNotifiction.createProfileLikeNotification).toBeCalledTimes(1)
+ expect(profileNotifiction.createProfileLikeNotification).toBeCalledWith(mockData)
+ })
- expect(profileNotifiction.createProfileLikeNotification).toBeCalledTimes(1);
- expect(profileNotifiction.createProfileLikeNotification).toBeCalledWith(mockData);
- });
+ it('should do nothing if there is already a like', async () => {
+ const mockProps = {
+ targetUserId: 'mockTargetUserId',
+ remove: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- it('should do nothing if there is already a like', async () => {
- const mockProps = {
- targetUserId: "mockTargetUserId",
- remove: false
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: true})
- (tryCatch as jest.Mock).mockResolvedValue({data: true});
+ const result: any = await likeProfile(mockProps, mockAuth, mockReq)
- const result: any = await likeProfile(mockProps, mockAuth, mockReq);
+ expect(result.status).toBe('success')
+ })
- expect(result.status).toBe('success');
- });
+ it('should remove a like', async () => {
+ const mockProps = {
+ targetUserId: 'mockTargetUserId',
+ remove: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockData = {
+ created_time: 'mockCreatedTime',
+ creator_id: 'mockCreatorId',
+ likeId: 'mockLikeId',
+ target_id: 'mockTargetId',
+ }
- it('should remove a like', async () => {
- const mockProps = {
- targetUserId: "mockTargetUserId",
- remove: true
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockData = {
- created_time: "mockCreatedTime",
- creator_id: "mockCreatorId",
- likeId: "mockLikeId",
- target_id: "mockTargetId"
- };
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockData, error: null})
- (tryCatch as jest.Mock).mockResolvedValue({data: mockData, error: null});
+ const result: any = await likeProfile(mockProps, mockAuth, mockReq)
- const result: any = await likeProfile(mockProps, mockAuth, mockReq);
+ expect(result.status).toBe('success')
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(
+ sqlMatch('delete from profile_likes where creator_id = $1 and target_id = $2'),
+ [mockAuth.uid, mockProps.targetUserId],
+ )
+ })
+ })
- expect(result.status).toBe('success');
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('delete from profile_likes where creator_id = $1 and target_id = $2'),
- [mockAuth.uid, mockProps.targetUserId]
- );
- });
- });
+ describe('when an error occurs', () => {
+ it('should throw if failed to remove like', async () => {
+ const mockProps = {
+ targetUserId: 'mockTargetUserId',
+ remove: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockData = {
+ created_time: 'mockCreatedTime',
+ creator_id: 'mockCreatorId',
+ likeId: 'mockLikeId',
+ target_id: 'mockTargetId',
+ }
- describe('when an error occurs', () => {
- it('should throw if failed to remove like', async () => {
- const mockProps = {
- targetUserId: "mockTargetUserId",
- remove: true
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockData = {
- created_time: "mockCreatedTime",
- creator_id: "mockCreatorId",
- likeId: "mockLikeId",
- target_id: "mockTargetId"
- };
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: mockData, error: Error})
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce({data: mockData, error: Error});
+ expect(likeProfile(mockProps, mockAuth, mockReq)).rejects.toThrow('Failed to remove like: ')
+ })
- expect(likeProfile(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Failed to remove like: ');
- });
+ it('should throw if user has already used their free like', async () => {
+ const mockProps = {
+ targetUserId: 'mockTargetUserId',
+ remove: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockData = {
+ created_time: 'mockCreatedTime',
+ creator_id: 'mockCreatorId',
+ likeId: 'mockLikeId',
+ target_id: 'mockTargetId',
+ }
- it('should throw if user has already used their free like', async () => {
- const mockProps = {
- targetUserId: "mockTargetUserId",
- remove: false
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockData = {
- created_time: "mockCreatedTime",
- creator_id: "mockCreatorId",
- likeId: "mockLikeId",
- target_id: "mockTargetId"
- };
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce({data: false})
+ .mockResolvedValueOnce({data: mockData, error: null})
+ ;(likeModules.getHasFreeLike as jest.Mock).mockResolvedValue(false)
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce({data: false})
- .mockResolvedValueOnce({data: mockData, error: null});
- (likeModules.getHasFreeLike as jest.Mock).mockResolvedValue(false);
+ expect(likeProfile(mockProps, mockAuth, mockReq)).rejects.toThrow(
+ 'You already liked someone today!',
+ )
+ })
+ it('should throw if failed to add like', async () => {
+ const mockProps = {
+ targetUserId: 'mockTargetUserId',
+ remove: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockData = {
+ created_time: 'mockCreatedTime',
+ creator_id: 'mockCreatorId',
+ likeId: 'mockLikeId',
+ target_id: 'mockTargetId',
+ }
- expect(likeProfile(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('You already liked someone today!');
- });
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce({data: false})
+ .mockResolvedValueOnce({data: mockData, error: Error})
+ ;(likeModules.getHasFreeLike as jest.Mock).mockResolvedValue(true)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(null)
- it('should throw if failed to add like', async () => {
- const mockProps = {
- targetUserId: "mockTargetUserId",
- remove: false
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockData = {
- created_time: "mockCreatedTime",
- creator_id: "mockCreatorId",
- likeId: "mockLikeId",
- target_id: "mockTargetId"
- };
-
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce({data: false})
- .mockResolvedValueOnce({data: mockData, error: Error});
- (likeModules.getHasFreeLike as jest.Mock).mockResolvedValue(true);
- (mockPg.one as jest.Mock).mockResolvedValue(null);
-
- expect(likeProfile(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Failed to add like: ');
- });
- });
-});
\ No newline at end of file
+ expect(likeProfile(mockProps, mockAuth, mockReq)).rejects.toThrow('Failed to add like: ')
+ })
+ })
+})
diff --git a/backend/api/tests/unit/mark-all-notifications-read.unit.test.ts b/backend/api/tests/unit/mark-all-notifications-read.unit.test.ts
index 62d34fff..7dbfd89c 100644
--- a/backend/api/tests/unit/mark-all-notifications-read.unit.test.ts
+++ b/backend/api/tests/unit/mark-all-notifications-read.unit.test.ts
@@ -1,38 +1,33 @@
-jest.mock('shared/supabase/init');
+jest.mock('shared/supabase/init')
-import {markAllNotifsRead} from "api/mark-all-notifications-read";
-import {AuthedUser} from "api/helpers/endpoint";
-import * as supabaseInit from "shared/supabase/init";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {markAllNotifsRead} from 'api/mark-all-notifications-read'
import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
describe('markAllNotifsRead', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- none: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should mark all notifications as read', async () => {
+ const mockProps = {} as any
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- describe('when given valid input', () => {
- it('should mark all notifications as read', async () => {
- const mockProps = {} as any;
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ await markAllNotifsRead(mockProps, mockAuth, mockReq)
- await markAllNotifsRead(mockProps, mockAuth, mockReq);
-
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('update user_notifications'),
- [mockAuth.uid]
- );
- });
- });
-});
\ No newline at end of file
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(sqlMatch('update user_notifications'), [mockAuth.uid])
+ })
+ })
+})
diff --git a/backend/api/tests/unit/react-to-message.unit.test.ts b/backend/api/tests/unit/react-to-message.unit.test.ts
index 7dd2dcf4..9ed6ba8f 100644
--- a/backend/api/tests/unit/react-to-message.unit.test.ts
+++ b/backend/api/tests/unit/react-to-message.unit.test.ts
@@ -1,140 +1,128 @@
-import {sqlMatch} from "common/test-utils";
-import {reactToMessage} from "api/react-to-message";
-import * as supabaseInit from "shared/supabase/init";
-import * as messageHelpers from "api/helpers/private-messages";
-import {AuthedUser} from "api/helpers/endpoint";
+import {AuthedUser} from 'api/helpers/endpoint'
+import * as messageHelpers from 'api/helpers/private-messages'
+import {reactToMessage} from 'api/react-to-message'
+import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
-jest.mock('shared/supabase/init');
-jest.mock('api/helpers/private-messages');
+jest.mock('shared/supabase/init')
+jest.mock('api/helpers/private-messages')
describe('reactToMessage', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- none: jest.fn()
- };
-
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should return success', async () => {
- const mockProps = {
- messageId: 123,
- reaction: "mockReaction",
- toDelete: false
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockMessage = { channel_id: "mockChannelId"};
+ describe('when given valid input', () => {
+ it('should return success', async () => {
+ const mockProps = {
+ messageId: 123,
+ reaction: 'mockReaction',
+ toDelete: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockMessage = {channel_id: 'mockChannelId'}
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (messageHelpers.broadcastPrivateMessages as jest.Mock).mockResolvedValue(null);
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(messageHelpers.broadcastPrivateMessages as jest.Mock).mockResolvedValue(null)
- const result = await reactToMessage(mockProps, mockAuth, mockReq);
- const [sql, params] = mockPg.oneOrNone.mock.calls[0]
- const [sql1, params1] = mockPg.none.mock.calls[0]
-
- expect(result.success).toBeTruthy();
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(params).toEqual([mockAuth.uid, mockProps.messageId])
- expect(sql).toEqual(
- sqlMatch('SELECT *')
- );
- expect(sql).toEqual(
- sqlMatch('FROM private_user_message_channel_members m')
- );
- expect(mockPg.none).toBeCalledTimes(1);
- expect(params1).toEqual([mockProps.reaction, mockAuth.uid, mockProps.messageId])
- expect(sql1).toEqual(
- sqlMatch('UPDATE private_user_messages')
- );
- expect(sql1).toEqual(
- sqlMatch('SET reactions =')
- );
- expect(messageHelpers.broadcastPrivateMessages).toBeCalledTimes(1);
- expect(messageHelpers.broadcastPrivateMessages).toBeCalledWith(
- expect.any(Object),
- mockMessage.channel_id,
- mockAuth.uid
- );
- });
+ const result = await reactToMessage(mockProps, mockAuth, mockReq)
+ const [sql, params] = mockPg.oneOrNone.mock.calls[0]
+ const [sql1, params1] = mockPg.none.mock.calls[0]
- it('should return success when removing a reaction', async () => {
- const mockProps = {
- messageId: 123,
- reaction: "mockReaction",
- toDelete: true
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockMessage = { channel_id: "mockChannelId"};
+ expect(result.success).toBeTruthy()
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(params).toEqual([mockAuth.uid, mockProps.messageId])
+ expect(sql).toEqual(sqlMatch('SELECT *'))
+ expect(sql).toEqual(sqlMatch('FROM private_user_message_channel_members m'))
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(params1).toEqual([mockProps.reaction, mockAuth.uid, mockProps.messageId])
+ expect(sql1).toEqual(sqlMatch('UPDATE private_user_messages'))
+ expect(sql1).toEqual(sqlMatch('SET reactions ='))
+ expect(messageHelpers.broadcastPrivateMessages).toBeCalledTimes(1)
+ expect(messageHelpers.broadcastPrivateMessages).toBeCalledWith(
+ expect.any(Object),
+ mockMessage.channel_id,
+ mockAuth.uid,
+ )
+ })
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (messageHelpers.broadcastPrivateMessages as jest.Mock).mockResolvedValue(null);
+ it('should return success when removing a reaction', async () => {
+ const mockProps = {
+ messageId: 123,
+ reaction: 'mockReaction',
+ toDelete: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockMessage = {channel_id: 'mockChannelId'}
- const result = await reactToMessage(mockProps, mockAuth, mockReq);
- const [sql, params] = mockPg.oneOrNone.mock.calls[0]
- const [sql1, params1] = mockPg.none.mock.calls[0]
-
- expect(result.success).toBeTruthy();
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledTimes(1);
- expect(params1).toEqual([mockProps.reaction, mockProps.messageId, mockAuth.uid])
- expect(sql1).toEqual(
- sqlMatch('UPDATE private_user_messages')
- );
- expect(sql1).toEqual(
- sqlMatch('SET reactions = reactions - $1')
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if user does not have the authorization to react', async () => {
- const mockProps = {
- messageId: 123,
- reaction: "mockReaction",
- toDelete: false
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(messageHelpers.broadcastPrivateMessages as jest.Mock).mockResolvedValue(null)
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(false);
+ const result = await reactToMessage(mockProps, mockAuth, mockReq)
+ const [_sql, _params] = mockPg.oneOrNone.mock.calls[0]
+ const [sql1, params1] = mockPg.none.mock.calls[0]
- expect(reactToMessage(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Not authorized to react to this message');
- });
+ expect(result.success).toBeTruthy()
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(params1).toEqual([mockProps.reaction, mockProps.messageId, mockAuth.uid])
+ expect(sql1).toEqual(sqlMatch('UPDATE private_user_messages'))
+ expect(sql1).toEqual(sqlMatch('SET reactions = reactions - $1'))
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if user does not have the authorization to react', async () => {
+ const mockProps = {
+ messageId: 123,
+ reaction: 'mockReaction',
+ toDelete: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- it('should return success', async () => {
- const mockProps = {
- messageId: 123,
- reaction: "mockReaction",
- toDelete: false
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockMessage = { channel_id: "mockChannelId"};
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(false)
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (messageHelpers.broadcastPrivateMessages as jest.Mock).mockRejectedValue(new Error('Broadcast error'));
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ expect(reactToMessage(mockProps, mockAuth, mockReq)).rejects.toThrow(
+ 'Not authorized to react to this message',
+ )
+ })
- await reactToMessage(mockProps, mockAuth, mockReq);
+ it('should return success', async () => {
+ const mockProps = {
+ messageId: 123,
+ reaction: 'mockReaction',
+ toDelete: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockMessage = {channel_id: 'mockChannelId'}
- expect(errorSpy).toBeCalledWith(
- expect.stringContaining('broadcastPrivateMessages failed'),
- expect.any(Error)
- );
- });
- });
-});
\ No newline at end of file
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockMessage)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(messageHelpers.broadcastPrivateMessages as jest.Mock).mockRejectedValue(
+ new Error('Broadcast error'),
+ )
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
+
+ await reactToMessage(mockProps, mockAuth, mockReq)
+
+ expect(errorSpy).toBeCalledWith(
+ expect.stringContaining('broadcastPrivateMessages failed'),
+ expect.any(Error),
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/remove-pinned-photo.unit.test.ts b/backend/api/tests/unit/remove-pinned-photo.unit.test.ts
index 2485d6c5..9d80e4f9 100644
--- a/backend/api/tests/unit/remove-pinned-photo.unit.test.ts
+++ b/backend/api/tests/unit/remove-pinned-photo.unit.test.ts
@@ -1,76 +1,74 @@
-jest.mock('shared/supabase/init');
-jest.mock('common/envs/constants');
-jest.mock('common/util/try-catch');
+jest.mock('shared/supabase/init')
+jest.mock('common/envs/constants')
+jest.mock('common/util/try-catch')
-import {removePinnedPhoto} from "api/remove-pinned-photo";
-import * as supabaseInit from "shared/supabase/init";
-import * as envConstants from "common/envs/constants";
-import {tryCatch} from "common/util/try-catch";
-import {AuthedUser} from "api/helpers/endpoint";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {removePinnedPhoto} from 'api/remove-pinned-photo'
+import * as envConstants from 'common/envs/constants'
import {sqlMatch} from 'common/test-utils'
+import {tryCatch} from 'common/util/try-catch'
+import * as supabaseInit from 'shared/supabase/init'
describe('removePinnedPhoto', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- none: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should return success', async () => {
+ const mockBody = {userId: 'mockUserId'}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- describe('when given valid input', () => {
- it('should return success', async () => {
- const mockBody = { userId: "mockUserId"};
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ jest.spyOn(envConstants, 'isAdminId').mockReturnValue(true)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({error: null})
- jest.spyOn(envConstants, 'isAdminId').mockReturnValue(true);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({error: null});
+ const result: any = await removePinnedPhoto(mockBody, mockAuth, mockReq)
- const result: any = await removePinnedPhoto(mockBody, mockAuth, mockReq);
+ expect(result.success).toBeTruthy()
+ expect(envConstants.isAdminId).toBeCalledTimes(1)
+ expect(envConstants.isAdminId).toBeCalledWith(mockAuth.uid)
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(
+ sqlMatch('update profiles set pinned_url = null where user_id = $1'),
+ [mockBody.userId],
+ )
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if user auth is not an admin', async () => {
+ const mockBody = {userId: 'mockUserId'}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- expect(result.success).toBeTruthy();
- expect(envConstants.isAdminId).toBeCalledTimes(1);
- expect(envConstants.isAdminId).toBeCalledWith(mockAuth.uid);
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('update profiles set pinned_url = null where user_id = $1'),
- [mockBody.userId]
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if user auth is not an admin', async () => {
- const mockBody = { userId: "mockUserId"};
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ jest.spyOn(envConstants, 'isAdminId').mockReturnValue(false)
- jest.spyOn(envConstants, 'isAdminId').mockReturnValue(false);
+ expect(removePinnedPhoto(mockBody, mockAuth, mockReq)).rejects.toThrow(
+ 'Only admins can remove pinned photo',
+ )
+ })
- expect(removePinnedPhoto(mockBody, mockAuth, mockReq))
- .rejects
- .toThrow('Only admins can remove pinned photo');
- });
+ it('should throw if failed to remove the pinned photo', async () => {
+ const mockBody = {userId: 'mockUserId'}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- it('should throw if failed to remove the pinned photo', async () => {
- const mockBody = { userId: "mockUserId"};
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ jest.spyOn(envConstants, 'isAdminId').mockReturnValue(true)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({error: Error})
- jest.spyOn(envConstants, 'isAdminId').mockReturnValue(true);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({error: Error});
-
- expect(removePinnedPhoto(mockBody, mockAuth, mockReq))
- .rejects
- .toThrow('Failed to remove pinned photo');
- });
- });
-});
\ No newline at end of file
+ expect(removePinnedPhoto(mockBody, mockAuth, mockReq)).rejects.toThrow(
+ 'Failed to remove pinned photo',
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/report.unit.test.ts b/backend/api/tests/unit/report.unit.test.ts
index 8d3af70d..07b7c784 100644
--- a/backend/api/tests/unit/report.unit.test.ts
+++ b/backend/api/tests/unit/report.unit.test.ts
@@ -1,226 +1,212 @@
-import {sqlMatch} from "common/test-utils";
-import {report} from "api/report";
-import * as supabaseInit from "shared/supabase/init";
-import {tryCatch} from "common/util/try-catch";
-import * as supabaseUtils from "shared/supabase/utils";
-import {sendDiscordMessage} from "common/discord/core";
-import {AuthedUser} from "api/helpers/endpoint";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {report} from 'api/report'
+import {sendDiscordMessage} from 'common/discord/core'
+import {sqlMatch} from 'common/test-utils'
+import {tryCatch} from 'common/util/try-catch'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseUtils from 'shared/supabase/utils'
-jest.mock('shared/supabase/init');
-jest.mock('common/util/try-catch');
-jest.mock('shared/supabase/utils');
-jest.mock('common/discord/core');
+jest.mock('shared/supabase/init')
+jest.mock('common/util/try-catch')
+jest.mock('shared/supabase/utils')
+jest.mock('common/discord/core')
describe('report', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should successfully file a report', async () => {
+ const mockBody = {
+ contentOwnerId: 'mockContentOwnerId',
+ contentType: 'user' as 'user' | 'comment' | 'contract',
+ contentId: 'mockContentId',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockReporter = {
+ created_time: 'mockCreatedTime',
+ data: {mockData: 'mockDataValue'},
+ id: 'mockId',
+ name: 'mockName',
+ name_username_vector: 'mockNameUsernameVector',
+ username: 'mockUsername',
+ }
+ const mockReported = {
+ created_time: 'mockCreatedTimeReported',
+ data: {mockDataReported: 'mockDataValueReported'},
+ id: 'mockIdReported',
+ name: 'mockNameReported',
+ name_username_vector: 'mockNameUsernameVectorReported',
+ username: 'mockUsernameReported',
+ }
- describe('when given valid input', () => {
- it('should successfully file a report', async () => {
- const mockBody = {
- contentOwnerId: "mockContentOwnerId",
- contentType: "user" as "user" | "comment" | "contract",
- contentId: "mockContentId",
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockReporter = {
- created_time: "mockCreatedTime",
- data: {"mockData" : "mockDataValue"},
- id: "mockId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername",
- };
- const mockReported = {
- created_time: "mockCreatedTimeReported",
- data: {"mockDataReported" : "mockDataValueReported"},
- id: "mockIdReported",
- name: "mockNameReported",
- name_username_vector: "mockNameUsernameVectorReported",
- username: "mockUsernameReported",
- };
+ ;(supabaseUtils.insert as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockBody, error: null})
- (supabaseUtils.insert as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({data: mockBody, error: null});
+ const result = await report(mockBody, mockAuth, mockReq)
- const result = await report(mockBody, mockAuth, mockReq);
+ expect(result.success).toBeTruthy()
+ expect(result.result).toStrictEqual({})
+ ;(mockPg.oneOrNone as jest.Mock).mockReturnValueOnce(null).mockReturnValueOnce(null)
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce({data: mockReporter, error: null})
+ .mockResolvedValueOnce({data: mockReported, error: null})
+ ;(sendDiscordMessage as jest.Mock).mockResolvedValue(null)
- expect(result.success).toBeTruthy();
- expect(result.result).toStrictEqual({});
+ await result.continue()
- (mockPg.oneOrNone as jest.Mock)
- .mockReturnValueOnce(null)
- .mockReturnValueOnce(null);
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce({data: mockReporter, error: null})
- .mockResolvedValueOnce({data: mockReported, error: null});
- (sendDiscordMessage as jest.Mock).mockResolvedValue(null);
+ expect(mockPg.oneOrNone).toBeCalledTimes(2)
+ expect(mockPg.oneOrNone).toHaveBeenNthCalledWith(
+ 1,
+ sqlMatch('select * from users where id = $1'),
+ [mockAuth.uid],
+ )
+ expect(mockPg.oneOrNone).toHaveBeenNthCalledWith(
+ 2,
+ sqlMatch('select * from users where id = $1'),
+ [mockBody.contentOwnerId],
+ )
+ expect(sendDiscordMessage).toBeCalledTimes(1)
+ expect(sendDiscordMessage).toBeCalledWith(
+ expect.stringContaining('**New Report**'),
+ 'reports',
+ )
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if failed to create the report', async () => {
+ const mockBody = {
+ contentOwnerId: 'mockContentOwnerId',
+ contentType: 'user' as 'user' | 'comment' | 'contract',
+ contentId: 'mockContentId',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- await result.continue();
+ ;(supabaseUtils.insert as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error})
- expect(mockPg.oneOrNone).toBeCalledTimes(2);
- expect(mockPg.oneOrNone).toHaveBeenNthCalledWith(
- 1,
- sqlMatch('select * from users where id = $1'),
- [mockAuth.uid]
- );
- expect(mockPg.oneOrNone).toHaveBeenNthCalledWith(
- 2,
- sqlMatch('select * from users where id = $1'),
- [mockBody.contentOwnerId]
- );
- expect(sendDiscordMessage).toBeCalledTimes(1);
- expect(sendDiscordMessage).toBeCalledWith(
- expect.stringContaining('**New Report**'),
- 'reports'
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if failed to create the report', async () => {
- const mockBody = {
- contentOwnerId: "mockContentOwnerId",
- contentType: "user" as "user" | "comment" | "contract",
- contentId: "mockContentId",
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ expect(report(mockBody, mockAuth, mockReq)).rejects.toThrow('Failed to create report: ')
+ })
- (supabaseUtils.insert as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error});
+ it('should throw if unable to get information about the user', async () => {
+ const mockBody = {
+ contentOwnerId: 'mockContentOwnerId',
+ contentType: 'user' as 'user' | 'comment' | 'contract',
+ contentId: 'mockContentId',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- expect(report(mockBody, mockAuth, mockReq))
- .rejects
- .toThrow('Failed to create report: ');
- });
+ ;(supabaseUtils.insert as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockBody, error: null})
- it('should throw if unable to get information about the user', async () => {
- const mockBody = {
- contentOwnerId: "mockContentOwnerId",
- contentType: "user" as "user" | "comment" | "contract",
- contentId: "mockContentId",
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ const result = await report(mockBody, mockAuth, mockReq)
- (supabaseUtils.insert as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({data: mockBody, error: null});
+ ;(mockPg.oneOrNone as jest.Mock).mockReturnValueOnce(null)
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({data: null, error: Error})
- const result = await report(mockBody, mockAuth, mockReq);
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
- (mockPg.oneOrNone as jest.Mock)
- .mockReturnValueOnce(null);
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce({data: null, error: Error});
+ await result.continue()
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ expect(errorSpy).toBeCalledWith(
+ expect.stringContaining('Failed to get user for report'),
+ expect.objectContaining({name: 'Error'}),
+ )
+ })
- await result.continue();
+ it('should throw if unable to get information about the user being reported', async () => {
+ const mockBody = {
+ contentOwnerId: 'mockContentOwnerId',
+ contentType: 'user' as 'user' | 'comment' | 'contract',
+ contentId: 'mockContentId',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockReporter = {
+ created_time: 'mockCreatedTime',
+ data: {mockData: 'mockDataValue'},
+ id: 'mockId',
+ name: 'mockName',
+ name_username_vector: 'mockNameUsernameVector',
+ username: 'mockUsername',
+ }
- expect(errorSpy).toBeCalledWith(
- expect.stringContaining('Failed to get user for report'),
- expect.objectContaining({name: 'Error'})
- );
- });
+ ;(supabaseUtils.insert as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockBody, error: null})
- it('should throw if unable to get information about the user being reported', async () => {
- const mockBody = {
- contentOwnerId: "mockContentOwnerId",
- contentType: "user" as "user" | "comment" | "contract",
- contentId: "mockContentId",
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockReporter = {
- created_time: "mockCreatedTime",
- data: {"mockData" : "mockDataValue"},
- id: "mockId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername",
- };
+ const result = await report(mockBody, mockAuth, mockReq)
- (supabaseUtils.insert as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({data: mockBody, error: null});
+ ;(mockPg.oneOrNone as jest.Mock).mockReturnValueOnce(null).mockReturnValueOnce(null)
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce({data: mockReporter, error: null})
+ .mockResolvedValueOnce({data: null, error: Error})
- const result = await report(mockBody, mockAuth, mockReq);
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
- (mockPg.oneOrNone as jest.Mock)
- .mockReturnValueOnce(null)
- .mockReturnValueOnce(null);
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce({data: mockReporter, error: null})
- .mockResolvedValueOnce({data: null, error: Error});
-
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ await result.continue()
- await result.continue();
+ expect(errorSpy).toBeCalledWith(
+ expect.stringContaining('Failed to get reported user for report'),
+ expect.objectContaining({name: 'Error'}),
+ )
+ })
- expect(errorSpy).toBeCalledWith(
- expect.stringContaining('Failed to get reported user for report'),
- expect.objectContaining({name: 'Error'})
- );
- });
+ it('should throw if failed to send discord report', async () => {
+ const mockBody = {
+ contentOwnerId: 'mockContentOwnerId',
+ contentType: 'user' as 'user' | 'comment' | 'contract',
+ contentId: 'mockContentId',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockReporter = {
+ created_time: 'mockCreatedTime',
+ data: {mockData: 'mockDataValue'},
+ id: 'mockId',
+ name: 'mockName',
+ name_username_vector: 'mockNameUsernameVector',
+ username: 'mockUsername',
+ }
+ const mockReported = {
+ created_time: 'mockCreatedTimeReported',
+ data: {mockDataReported: 'mockDataValueReported'},
+ id: 'mockIdReported',
+ name: 'mockNameReported',
+ name_username_vector: 'mockNameUsernameVectorReported',
+ username: 'mockUsernameReported',
+ }
- it('should throw if failed to send discord report', async () => {
- const mockBody = {
- contentOwnerId: "mockContentOwnerId",
- contentType: "user" as "user" | "comment" | "contract",
- contentId: "mockContentId",
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockReporter = {
- created_time: "mockCreatedTime",
- data: {"mockData" : "mockDataValue"},
- id: "mockId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername",
- };
- const mockReported = {
- created_time: "mockCreatedTimeReported",
- data: {"mockDataReported" : "mockDataValueReported"},
- id: "mockIdReported",
- name: "mockNameReported",
- name_username_vector: "mockNameUsernameVectorReported",
- username: "mockUsernameReported",
- };
+ ;(supabaseUtils.insert as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockBody, error: null})
- (supabaseUtils.insert as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({data: mockBody, error: null});
+ const result = await report(mockBody, mockAuth, mockReq)
- const result = await report(mockBody, mockAuth, mockReq);
+ ;(mockPg.oneOrNone as jest.Mock).mockReturnValueOnce(null).mockReturnValueOnce(null)
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce({data: mockReporter, error: null})
+ .mockResolvedValueOnce({data: mockReported, error: null})
+ ;(sendDiscordMessage as jest.Mock).mockRejectedValue(new Error('Discord error'))
- (mockPg.oneOrNone as jest.Mock)
- .mockReturnValueOnce(null)
- .mockReturnValueOnce(null);
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce({data: mockReporter, error: null})
- .mockResolvedValueOnce({data: mockReported, error: null});
- (sendDiscordMessage as jest.Mock).mockRejectedValue(new Error('Discord error'));
+ const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
+ await result.continue()
- await result.continue();
-
- expect(errorSpy).toBeCalledWith(
- expect.stringContaining('Failed to send discord reports'),
- expect.any(Error)
- );
-
- });
- });
-});
\ No newline at end of file
+ expect(errorSpy).toBeCalledWith(
+ expect.stringContaining('Failed to send discord reports'),
+ expect.any(Error),
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/save-subscription-mobile.unit.test.ts b/backend/api/tests/unit/save-subscription-mobile.unit.test.ts
index 96915775..8ea7409b 100644
--- a/backend/api/tests/unit/save-subscription-mobile.unit.test.ts
+++ b/backend/api/tests/unit/save-subscription-mobile.unit.test.ts
@@ -1,71 +1,68 @@
-import {sqlMatch} from "common/test-utils";
-import {AuthedUser} from "api/helpers/endpoint";
-import {saveSubscriptionMobile} from "api/save-subscription-mobile";
-import * as supabaseInit from "shared/supabase/init";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {saveSubscriptionMobile} from 'api/save-subscription-mobile'
+import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
-jest.mock('shared/supabase/init');
+jest.mock('shared/supabase/init')
describe('saveSubscriptionMobile', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- none: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should return success after saving the subscription', async () => {
+ const mockBody = {token: 'mockToken'}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- describe('when given valid input', () => {
- it('should return success after saving the subscription', async () => {
- const mockBody = { token: "mockToken" };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
- (mockPg.none as jest.Mock).mockResolvedValue(null);
+ const result = await saveSubscriptionMobile(mockBody, mockAuth, mockReq)
- const result = await saveSubscriptionMobile(mockBody, mockAuth, mockReq);
+ expect(result.success).toBeTruthy()
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(
+ sqlMatch('insert into push_subscriptions_mobile(token, platform, user_id)'),
+ [mockBody.token, 'android', mockAuth.uid],
+ )
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if token is invalid', async () => {
+ const mockBody = {} as any
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- expect(result.success).toBeTruthy();
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('insert into push_subscriptions_mobile(token, platform, user_id)'),
- [mockBody.token, 'android', mockAuth.uid]
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if token is invalid', async () => {
- const mockBody = {} as any;
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ expect(saveSubscriptionMobile(mockBody, mockAuth, mockReq)).rejects.toThrow(
+ 'Invalid subscription object',
+ )
+ })
- expect(saveSubscriptionMobile(mockBody, mockAuth, mockReq))
- .rejects
- .toThrow('Invalid subscription object');
+ it('should throw if unable to save subscription', async () => {
+ const mockBody = {token: 'mockToken'}
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- });
+ ;(mockPg.none as jest.Mock).mockRejectedValue(new Error('Saving error'))
+ const _errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
- it('should throw if unable to save subscription', async () => {
- const mockBody = { token: "mockToken" };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
-
- (mockPg.none as jest.Mock).mockRejectedValue(new Error('Saving error'));
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
-
- expect(saveSubscriptionMobile(mockBody, mockAuth, mockReq))
- .rejects
- .toThrow('Failed to save subscription');
- // expect(errorSpy).toBeCalledTimes(1);
- // expect(errorSpy).toBeCalledWith(
- // expect.stringContaining('Error saving subscription'),
- // expect.any(Error)
- // );
- });
- });
-});
\ No newline at end of file
+ expect(saveSubscriptionMobile(mockBody, mockAuth, mockReq)).rejects.toThrow(
+ 'Failed to save subscription',
+ )
+ // expect(errorSpy).toBeCalledTimes(1);
+ // expect(errorSpy).toBeCalledWith(
+ // expect.stringContaining('Error saving subscription'),
+ // expect.any(Error)
+ // );
+ })
+ })
+})
diff --git a/backend/api/tests/unit/save-subscription.unit.test.ts b/backend/api/tests/unit/save-subscription.unit.test.ts
index 7ce43b68..6e1272ac 100644
--- a/backend/api/tests/unit/save-subscription.unit.test.ts
+++ b/backend/api/tests/unit/save-subscription.unit.test.ts
@@ -1,119 +1,117 @@
-jest.mock('shared/supabase/init');
+jest.mock('shared/supabase/init')
-import {AuthedUser} from "api/helpers/endpoint";
-import {saveSubscription} from "api/save-subscription";
-import * as supabaseInit from "shared/supabase/init";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {saveSubscription} from 'api/save-subscription'
import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
describe('saveSubscription', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- none: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should save user subscription', async () => {
+ const mockBody = {
+ subscription: {
+ endpoint: 'mockEndpoint',
+ keys: 'mockKeys',
+ },
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockExists = {id: 'mockId'}
- describe('when given valid input', () => {
- it('should save user subscription', async () => {
- const mockBody = {
- subscription: {
- endpoint: "mockEndpoint",
- keys: "mockKeys",
- }
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockExists = { id: "mockId" };
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockExists)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockExists);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
-
- const result = await saveSubscription(mockBody, mockAuth, mockReq);
+ const result = await saveSubscription(mockBody, mockAuth, mockReq)
- expect(result.success).toBeTruthy();
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select id from push_subscriptions where endpoint = $1'),
- [mockBody.subscription.endpoint]
- );
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('update push_subscriptions set keys = $1, user_id = $2 where id = $3'),
- [mockBody.subscription.keys, mockAuth.uid, mockExists.id]
- );
- });
+ expect(result.success).toBeTruthy()
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(
+ sqlMatch('select id from push_subscriptions where endpoint = $1'),
+ [mockBody.subscription.endpoint],
+ )
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(
+ sqlMatch('update push_subscriptions set keys = $1, user_id = $2 where id = $3'),
+ [mockBody.subscription.keys, mockAuth.uid, mockExists.id],
+ )
+ })
- it('should save user subscription even if this is their first one', async () => {
- const mockBody = {
- subscription: {
- endpoint: "mockEndpoint",
- keys: "mockKeys",
- }
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ it('should save user subscription even if this is their first one', async () => {
+ const mockBody = {
+ subscription: {
+ endpoint: 'mockEndpoint',
+ keys: 'mockKeys',
+ },
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(false);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
-
- const result = await saveSubscription(mockBody, mockAuth, mockReq);
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(false)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
- expect(result.success).toBeTruthy();
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select id from push_subscriptions where endpoint = $1'),
- [mockBody.subscription.endpoint]
- );
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('insert into push_subscriptions(endpoint, keys, user_id) values($1, $2, $3)'),
- [mockBody.subscription.endpoint, mockBody.subscription.keys, mockAuth.uid]
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if the subscription object is invalid', async () => {
- const mockBody = {} as any;
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
-
- expect(saveSubscription(mockBody, mockAuth, mockReq))
- .rejects
- .toThrow('Invalid subscription object');
- });
+ const result = await saveSubscription(mockBody, mockAuth, mockReq)
- it('should throw if unable to save subscription', async () => {
- const mockBody = {
- subscription: {
- endpoint: "mockEndpoint",
- keys: "mockKeys",
- }
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockExists = { id: "mockId" };
+ expect(result.success).toBeTruthy()
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(
+ sqlMatch('select id from push_subscriptions where endpoint = $1'),
+ [mockBody.subscription.endpoint],
+ )
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(
+ sqlMatch('insert into push_subscriptions(endpoint, keys, user_id) values($1, $2, $3)'),
+ [mockBody.subscription.endpoint, mockBody.subscription.keys, mockAuth.uid],
+ )
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if the subscription object is invalid', async () => {
+ const mockBody = {} as any
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockExists);
- (mockPg.none as jest.Mock).mockRejectedValue(new Error('Saving error'));
- const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {});
-
- expect(saveSubscription(mockBody, mockAuth, mockReq))
- .rejects
- .toThrow('Failed to save subscription');
+ expect(saveSubscription(mockBody, mockAuth, mockReq)).rejects.toThrow(
+ 'Invalid subscription object',
+ )
+ })
- // expect(errorSpy).toBeCalledTimes(1);
- // expect(errorSpy).toBeCalledWith(
- // expect.stringContaining('Error saving subscription'),
- // expect.any(Error)
- // );
- });
- });
-});
\ No newline at end of file
+ it('should throw if unable to save subscription', async () => {
+ const mockBody = {
+ subscription: {
+ endpoint: 'mockEndpoint',
+ keys: 'mockKeys',
+ },
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockExists = {id: 'mockId'}
+
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockExists)
+ ;(mockPg.none as jest.Mock).mockRejectedValue(new Error('Saving error'))
+ const _errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
+
+ expect(saveSubscription(mockBody, mockAuth, mockReq)).rejects.toThrow(
+ 'Failed to save subscription',
+ )
+
+ // expect(errorSpy).toBeCalledTimes(1);
+ // expect(errorSpy).toBeCalledWith(
+ // expect.stringContaining('Error saving subscription'),
+ // expect.any(Error)
+ // );
+ })
+ })
+})
diff --git a/backend/api/tests/unit/search-location.unit.test.ts b/backend/api/tests/unit/search-location.unit.test.ts
index f95a3f10..4405529d 100644
--- a/backend/api/tests/unit/search-location.unit.test.ts
+++ b/backend/api/tests/unit/search-location.unit.test.ts
@@ -1,36 +1,38 @@
-jest.mock('common/geodb');
+jest.mock('common/geodb')
-import { AuthedUser } from "api/helpers/endpoint";
-import { searchLocation } from "api/search-location";
-import * as geodbModules from "common/geodb";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {searchLocation} from 'api/search-location'
+import * as geodbModules from 'common/geodb'
describe('searchLocation', () => {
- beforeEach(() => {
- jest.resetAllMocks();
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ beforeEach(() => {
+ jest.resetAllMocks()
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should return search location', async () => {
- const mockBody = {
- term: "mockTerm",
- limit: 15
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockReturn = "Pass";
+ describe('when given valid input', () => {
+ it('should return search location', async () => {
+ const mockBody = {
+ term: 'mockTerm',
+ limit: 15,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockReturn = 'Pass'
- (geodbModules.geodbFetch as jest.Mock).mockResolvedValue(mockReturn);
+ ;(geodbModules.geodbFetch as jest.Mock).mockResolvedValue(mockReturn)
- const result = await searchLocation(mockBody, mockAuth, mockReq);
+ const result = await searchLocation(mockBody, mockAuth, mockReq)
- expect(result).toBe(mockReturn);
- expect(geodbModules.geodbFetch).toBeCalledTimes(1);
- expect(geodbModules.geodbFetch).toBeCalledWith(
- expect.stringContaining(`/cities?namePrefix=${mockBody.term}&limit=${mockBody.limit}&offset=0&sort=-population`)
- );
- });
- });
-});
\ No newline at end of file
+ expect(result).toBe(mockReturn)
+ expect(geodbModules.geodbFetch).toBeCalledTimes(1)
+ expect(geodbModules.geodbFetch).toBeCalledWith(
+ expect.stringContaining(
+ `/cities?namePrefix=${mockBody.term}&limit=${mockBody.limit}&offset=0&sort=-population`,
+ ),
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/search-near-city.unit.test.ts b/backend/api/tests/unit/search-near-city.unit.test.ts
index 84a337e1..bd265c46 100644
--- a/backend/api/tests/unit/search-near-city.unit.test.ts
+++ b/backend/api/tests/unit/search-near-city.unit.test.ts
@@ -1,72 +1,74 @@
-jest.mock('common/geodb');
+jest.mock('common/geodb')
-import * as citySearchModules from "api/search-near-city";
-import * as geoDbModules from "common/geodb";
-import { AuthedUser } from "api/helpers/endpoint";
+import {AuthedUser} from 'api/helpers/endpoint'
+import * as citySearchModules from 'api/search-near-city'
+import * as geoDbModules from 'common/geodb'
describe('searchNearCity', () => {
- beforeEach(() => {
- jest.resetAllMocks();
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ beforeEach(() => {
+ jest.resetAllMocks()
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should return locations near a city', async () => {
- const mockBody = {
- radius: 123,
- cityId: "mockCityId"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockReturn = "Pass";
+ describe('when given valid input', () => {
+ it('should return locations near a city', async () => {
+ const mockBody = {
+ radius: 123,
+ cityId: 'mockCityId',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockReturn = 'Pass'
- (geoDbModules.geodbFetch as jest.Mock).mockResolvedValue(mockReturn);
-
- const result = await citySearchModules.searchNearCity(mockBody, mockAuth, mockReq);
+ ;(geoDbModules.geodbFetch as jest.Mock).mockResolvedValue(mockReturn)
- expect(result).toBe(mockReturn);
- expect(geoDbModules.geodbFetch).toBeCalledTimes(1);
- expect(geoDbModules.geodbFetch).toBeCalledWith(
- expect.stringContaining(`/cities/${mockBody.cityId}/nearbyCities?radius=${mockBody.radius}&offset=0&sort=-population&limit=100`)
- );
- });
- });
-});
+ const result = await citySearchModules.searchNearCity(mockBody, mockAuth, mockReq)
+
+ expect(result).toBe(mockReturn)
+ expect(geoDbModules.geodbFetch).toBeCalledTimes(1)
+ expect(geoDbModules.geodbFetch).toBeCalledWith(
+ expect.stringContaining(
+ `/cities/${mockBody.cityId}/nearbyCities?radius=${mockBody.radius}&offset=0&sort=-population&limit=100`,
+ ),
+ )
+ })
+ })
+})
describe('getNearCity', () => {
- beforeEach(() => {
- jest.resetAllMocks();
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ beforeEach(() => {
+ jest.resetAllMocks()
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should return locations near a city', async () => {
- const mockBody = {
- radius: 123,
- cityId: "mockCityId"
- };
- const mockReturn = {
- status: "mockStatus",
- data: {
- data: [
- { id: "mockId" }
- ]
- }
- };
+ describe('when given valid input', () => {
+ it('should return locations near a city', async () => {
+ const mockBody = {
+ radius: 123,
+ cityId: 'mockCityId',
+ }
+ const mockReturn = {
+ status: 'mockStatus',
+ data: {
+ data: [{id: 'mockId'}],
+ },
+ }
- (geoDbModules.geodbFetch as jest.Mock).mockResolvedValue(mockReturn);
-
- const result = await citySearchModules.getNearbyCities(mockBody.cityId, mockBody.radius);
+ ;(geoDbModules.geodbFetch as jest.Mock).mockResolvedValue(mockReturn)
- expect(result).toStrictEqual([mockReturn.data.data[0].id]);
- expect(geoDbModules.geodbFetch).toBeCalledTimes(1);
- expect(geoDbModules.geodbFetch).toBeCalledWith(
- expect.stringContaining(`/cities/${mockBody.cityId}/nearbyCities?radius=${mockBody.radius}&offset=0&sort=-population&limit=100`)
- );
- });
- });
-});
+ const result = await citySearchModules.getNearbyCities(mockBody.cityId, mockBody.radius)
+
+ expect(result).toStrictEqual([mockReturn.data.data[0].id])
+ expect(geoDbModules.geodbFetch).toBeCalledTimes(1)
+ expect(geoDbModules.geodbFetch).toBeCalledWith(
+ expect.stringContaining(
+ `/cities/${mockBody.cityId}/nearbyCities?radius=${mockBody.radius}&offset=0&sort=-population&limit=100`,
+ ),
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/search-users.unit.test.ts b/backend/api/tests/unit/search-users.unit.test.ts
index d905b72f..d65ff5da 100644
--- a/backend/api/tests/unit/search-users.unit.test.ts
+++ b/backend/api/tests/unit/search-users.unit.test.ts
@@ -1,155 +1,139 @@
-import {sqlMatch} from "common/test-utils";
-import {searchUsers} from "api/search-users";
-import * as supabaseInit from "shared/supabase/init";
-import * as searchHelpers from "shared/helpers/search";
-import * as sqlBuilderModules from "shared/supabase/sql-builder";
-import * as supabaseUsers from "common/supabase/users";
-import {toUserAPIResponse} from "common/api/user-types";
-import {AuthedUser} from "api/helpers/endpoint";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {searchUsers} from 'api/search-users'
+import {toUserAPIResponse} from 'common/api/user-types'
+import * as supabaseUsers from 'common/supabase/users'
+import {sqlMatch} from 'common/test-utils'
+import * as searchHelpers from 'shared/helpers/search'
+import * as supabaseInit from 'shared/supabase/init'
+import * as sqlBuilderModules from 'shared/supabase/sql-builder'
-jest.mock('shared/supabase/init');
-jest.mock('shared/helpers/search');
-jest.mock('shared/supabase/sql-builder');
-jest.mock('common/supabase/users');
-jest.mock('common/api/user-types');
+jest.mock('shared/supabase/init')
+jest.mock('shared/helpers/search')
+jest.mock('shared/supabase/sql-builder')
+jest.mock('common/supabase/users')
+jest.mock('common/api/user-types')
describe('searchUsers', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- map: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ map: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks()
- });
+ describe('when given valid input', () => {
+ it('should return an array of uniq users', async () => {
+ const mockProps = {
+ term: 'mockTerm',
+ limit: 10,
+ page: 1,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockSearchAllSql = 'mockSQL'
+ const mockAllUser = [{id: 'mockId 1'}, {id: 'mockId 2'}, {id: 'mockId 3'}]
- describe('when given valid input', () => {
- it('should return an array of uniq users', async () => {
- const mockProps = {
- term: "mockTerm",
- limit: 10,
- page: 1
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockSearchAllSql = "mockSQL";
- const mockAllUser = [
- {id: "mockId 1"},
- {id: "mockId 2"},
- {id: "mockId 3"},
- ];
+ ;(sqlBuilderModules.renderSql as jest.Mock).mockReturnValue(mockSearchAllSql)
+ ;(sqlBuilderModules.select as jest.Mock).mockReturnValue('Select')
+ ;(sqlBuilderModules.from as jest.Mock).mockReturnValue('From')
+ ;(sqlBuilderModules.where as jest.Mock).mockReturnValue('Where')
+ ;(searchHelpers.constructPrefixTsQuery as jest.Mock).mockReturnValue('ConstructPrefix')
+ ;(sqlBuilderModules.orderBy as jest.Mock).mockReturnValue('OrderBy')
+ ;(sqlBuilderModules.limit as jest.Mock).mockReturnValue('Limit')
+ ;(supabaseUsers.convertUser as jest.Mock).mockResolvedValue(null)
+ ;(mockPg.map as jest.Mock).mockResolvedValue(mockAllUser)
+ ;(toUserAPIResponse as jest.Mock)
+ .mockReturnValueOnce(mockAllUser[0].id)
+ .mockReturnValueOnce(mockAllUser[1].id)
+ .mockReturnValueOnce(mockAllUser[2].id)
- (sqlBuilderModules.renderSql as jest.Mock).mockReturnValue(mockSearchAllSql);
- (sqlBuilderModules.select as jest.Mock).mockReturnValue('Select');
- (sqlBuilderModules.from as jest.Mock).mockReturnValue('From');
- (sqlBuilderModules.where as jest.Mock).mockReturnValue('Where');
- (searchHelpers.constructPrefixTsQuery as jest.Mock).mockReturnValue('ConstructPrefix');
- (sqlBuilderModules.orderBy as jest.Mock).mockReturnValue('OrderBy');
- (sqlBuilderModules.limit as jest.Mock).mockReturnValue('Limit');
- (supabaseUsers.convertUser as jest.Mock).mockResolvedValue(null);
- (mockPg.map as jest.Mock).mockResolvedValue(mockAllUser);
- (toUserAPIResponse as jest.Mock)
- .mockReturnValueOnce(mockAllUser[0].id)
- .mockReturnValueOnce(mockAllUser[1].id)
- .mockReturnValueOnce(mockAllUser[2].id);
+ const result: any = await searchUsers(mockProps, mockAuth, mockReq)
- const result: any = await searchUsers(mockProps, mockAuth, mockReq);
+ expect(result[0]).toContain(mockAllUser[0].id)
+ expect(result[1]).toContain(mockAllUser[1].id)
+ expect(result[2]).toContain(mockAllUser[2].id)
- expect(result[0]).toContain(mockAllUser[0].id);
- expect(result[1]).toContain(mockAllUser[1].id);
- expect(result[2]).toContain(mockAllUser[2].id);
+ expect(sqlBuilderModules.renderSql).toBeCalledTimes(1)
+ expect(sqlBuilderModules.renderSql).toBeCalledWith(
+ ['Select', 'From'],
+ ['Where', 'OrderBy'],
+ 'Limit',
+ )
- expect(sqlBuilderModules.renderSql).toBeCalledTimes(1);
- expect(sqlBuilderModules.renderSql).toBeCalledWith(
- ['Select', 'From'],
- ['Where', 'OrderBy'],
- 'Limit'
- );
-
- expect(sqlBuilderModules.select).toBeCalledTimes(1);
- expect(sqlBuilderModules.select).toBeCalledWith('*');
- expect(sqlBuilderModules.from).toBeCalledTimes(1);
- expect(sqlBuilderModules.from).toBeCalledWith('users');
- expect(sqlBuilderModules.where).toBeCalledTimes(1);
- expect(sqlBuilderModules.where).toBeCalledWith(
- sqlMatch("name_username_vector @@ websearch_to_tsquery('english', $1)"),
- [mockProps.term, 'ConstructPrefix']
- );
- expect(sqlBuilderModules.orderBy).toBeCalledTimes(1);
- expect(sqlBuilderModules.orderBy).toBeCalledWith(
- sqlMatch("ts_rank(name_username_vector, websearch_to_tsquery($1)) desc,"),
- [mockProps.term]
- );
- expect(sqlBuilderModules.limit).toBeCalledTimes(1);
- expect(sqlBuilderModules.limit).toBeCalledWith(mockProps.limit, mockProps.page * mockProps.limit);
- expect(mockPg.map).toBeCalledTimes(1);
- expect(mockPg.map).toBeCalledWith(
- mockSearchAllSql,
- null,
- expect.any(Function)
- );
- });
+ expect(sqlBuilderModules.select).toBeCalledTimes(1)
+ expect(sqlBuilderModules.select).toBeCalledWith('*')
+ expect(sqlBuilderModules.from).toBeCalledTimes(1)
+ expect(sqlBuilderModules.from).toBeCalledWith('users')
+ expect(sqlBuilderModules.where).toBeCalledTimes(1)
+ expect(sqlBuilderModules.where).toBeCalledWith(
+ sqlMatch("name_username_vector @@ websearch_to_tsquery('english', $1)"),
+ [mockProps.term, 'ConstructPrefix'],
+ )
+ expect(sqlBuilderModules.orderBy).toBeCalledTimes(1)
+ expect(sqlBuilderModules.orderBy).toBeCalledWith(
+ sqlMatch('ts_rank(name_username_vector, websearch_to_tsquery($1)) desc,'),
+ [mockProps.term],
+ )
+ expect(sqlBuilderModules.limit).toBeCalledTimes(1)
+ expect(sqlBuilderModules.limit).toBeCalledWith(
+ mockProps.limit,
+ mockProps.page * mockProps.limit,
+ )
+ expect(mockPg.map).toBeCalledTimes(1)
+ expect(mockPg.map).toBeCalledWith(mockSearchAllSql, null, expect.any(Function))
+ })
- it('should return an array of uniq users if no term is supplied', async () => {
- const mockProps = {
- limit: 10,
- page: 1
- } as any;
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockSearchAllSql = "mockSQL";
- const mockAllUser = [
- {id: "mockId 1"},
- {id: "mockId 2"},
- {id: "mockId 3"},
- ];
+ it('should return an array of uniq users if no term is supplied', async () => {
+ const mockProps = {
+ limit: 10,
+ page: 1,
+ } as any
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockSearchAllSql = 'mockSQL'
+ const mockAllUser = [{id: 'mockId 1'}, {id: 'mockId 2'}, {id: 'mockId 3'}]
- (sqlBuilderModules.renderSql as jest.Mock).mockReturnValue(mockSearchAllSql);
- (sqlBuilderModules.select as jest.Mock).mockReturnValue('Select');
- (sqlBuilderModules.from as jest.Mock).mockReturnValue('From');
- (sqlBuilderModules.orderBy as jest.Mock).mockReturnValue('OrderBy');
- (sqlBuilderModules.limit as jest.Mock).mockReturnValue('Limit');
- (supabaseUsers.convertUser as jest.Mock).mockResolvedValue(null);
- (mockPg.map as jest.Mock).mockResolvedValue(mockAllUser);
- (toUserAPIResponse as jest.Mock)
- .mockReturnValueOnce(mockAllUser[0].id)
- .mockReturnValueOnce(mockAllUser[1].id)
- .mockReturnValueOnce(mockAllUser[2].id);
+ ;(sqlBuilderModules.renderSql as jest.Mock).mockReturnValue(mockSearchAllSql)
+ ;(sqlBuilderModules.select as jest.Mock).mockReturnValue('Select')
+ ;(sqlBuilderModules.from as jest.Mock).mockReturnValue('From')
+ ;(sqlBuilderModules.orderBy as jest.Mock).mockReturnValue('OrderBy')
+ ;(sqlBuilderModules.limit as jest.Mock).mockReturnValue('Limit')
+ ;(supabaseUsers.convertUser as jest.Mock).mockResolvedValue(null)
+ ;(mockPg.map as jest.Mock).mockResolvedValue(mockAllUser)
+ ;(toUserAPIResponse as jest.Mock)
+ .mockReturnValueOnce(mockAllUser[0].id)
+ .mockReturnValueOnce(mockAllUser[1].id)
+ .mockReturnValueOnce(mockAllUser[2].id)
- const result: any = await searchUsers(mockProps, mockAuth, mockReq);
+ const result: any = await searchUsers(mockProps, mockAuth, mockReq)
- expect(result[0]).toContain(mockAllUser[0].id);
- expect(result[1]).toContain(mockAllUser[1].id);
- expect(result[2]).toContain(mockAllUser[2].id);
+ expect(result[0]).toContain(mockAllUser[0].id)
+ expect(result[1]).toContain(mockAllUser[1].id)
+ expect(result[2]).toContain(mockAllUser[2].id)
- expect(sqlBuilderModules.renderSql).toBeCalledTimes(1);
- expect(sqlBuilderModules.renderSql).toBeCalledWith(
- ['Select', 'From'],
- 'OrderBy',
- 'Limit'
- );
-
- expect(sqlBuilderModules.select).toBeCalledTimes(1);
- expect(sqlBuilderModules.select).toBeCalledWith('*');
- expect(sqlBuilderModules.from).toBeCalledTimes(1);
- expect(sqlBuilderModules.from).toBeCalledWith('users');
- expect(sqlBuilderModules.orderBy).toBeCalledTimes(1);
- expect(sqlBuilderModules.orderBy).toBeCalledWith(
- expect.stringMatching(`data->'creatorTraders'->'allTime' desc nulls last`)
- );
- expect(sqlBuilderModules.limit).toBeCalledTimes(1);
- expect(sqlBuilderModules.limit).toBeCalledWith(mockProps.limit, mockProps.page * mockProps.limit);
- expect(mockPg.map).toBeCalledTimes(1);
- expect(mockPg.map).toBeCalledWith(
- mockSearchAllSql,
- null,
- expect.any(Function)
- );
- });
- });
-});
\ No newline at end of file
+ expect(sqlBuilderModules.renderSql).toBeCalledTimes(1)
+ expect(sqlBuilderModules.renderSql).toBeCalledWith(['Select', 'From'], 'OrderBy', 'Limit')
+
+ expect(sqlBuilderModules.select).toBeCalledTimes(1)
+ expect(sqlBuilderModules.select).toBeCalledWith('*')
+ expect(sqlBuilderModules.from).toBeCalledTimes(1)
+ expect(sqlBuilderModules.from).toBeCalledWith('users')
+ expect(sqlBuilderModules.orderBy).toBeCalledTimes(1)
+ expect(sqlBuilderModules.orderBy).toBeCalledWith(
+ expect.stringMatching(`data->'creatorTraders'->'allTime' desc nulls last`),
+ )
+ expect(sqlBuilderModules.limit).toBeCalledTimes(1)
+ expect(sqlBuilderModules.limit).toBeCalledWith(
+ mockProps.limit,
+ mockProps.page * mockProps.limit,
+ )
+ expect(mockPg.map).toBeCalledTimes(1)
+ expect(mockPg.map).toBeCalledWith(mockSearchAllSql, null, expect.any(Function))
+ })
+ })
+})
diff --git a/backend/api/tests/unit/send-search-notifications.unit.test.ts b/backend/api/tests/unit/send-search-notifications.unit.test.ts
index 075487f7..ee8f604f 100644
--- a/backend/api/tests/unit/send-search-notifications.unit.test.ts
+++ b/backend/api/tests/unit/send-search-notifications.unit.test.ts
@@ -1,316 +1,221 @@
-jest.mock('shared/supabase/init');
-jest.mock('shared/supabase/sql-builder');
-jest.mock('api/get-profiles');
-jest.mock('email/functions/helpers');
-jest.mock('lodash');
+jest.mock('shared/supabase/init')
+jest.mock('shared/supabase/sql-builder')
+jest.mock('api/get-profiles')
+jest.mock('email/functions/helpers')
-import * as searchNotificationModules from "api/send-search-notifications";
-import * as supabaseInit from "shared/supabase/init";
-import * as sqlBuilderModules from "shared/supabase/sql-builder";
-import * as profileModules from "api/get-profiles";
-import * as helperModules from "email/functions/helpers";
-import * as lodashModules from "lodash";
+import * as profileModules from 'api/get-profiles'
+import * as searchNotificationModules from 'api/send-search-notifications'
+import * as helperModules from 'email/functions/helpers'
+import * as supabaseInit from 'shared/supabase/init'
+import * as sqlBuilderModules from 'shared/supabase/sql-builder'
describe('sendSearchNotification', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- map: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ map: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should send search notification emails', async () => {
+ const mockSearchQuery = 'mockSqlQuery'
+ const mockSearches = [
+ {
+ created_time: 'mockSearchCreatedTime',
+ creator_id: 'mockCreatorId',
+ id: 123,
+ last_notified_at: null,
+ location: {mockLocation: 'mockLocationValue'},
+ search_filters: null,
+ search_name: null,
+ },
+ {
+ created_time: 'mockCreatedTime1',
+ creator_id: 'mockCreatorId1',
+ id: 1234,
+ last_notified_at: null,
+ location: {mockLocation1: 'mockLocationValue1'},
+ search_filters: null,
+ search_name: null,
+ },
+ ]
+ const _mockUsers = [
+ {
+ created_time: 'mockUserCreatedTime',
+ data: {mockData: 'mockDataValue'},
+ id: 'mockId',
+ name: 'mockName',
+ name_username_vector: 'mockNameUsernameVector',
+ username: 'mockUsername',
+ },
+ {
+ created_time: 'mockUserCreatedTime1',
+ data: {mockData1: 'mockDataValue1'},
+ id: 'mockId1',
+ name: 'mockName1',
+ name_username_vector: 'mockNameUsernameVector1',
+ username: 'mockUsername1',
+ },
+ ]
+ const _mockPrivateUsers = [
+ {
+ data: {mockData: 'mockDataValue'},
+ id: 'mockId',
+ },
+ {
+ data: {mockData1: 'mockDataValue1'},
+ id: 'mockId1',
+ },
+ ]
+ const mockProfiles = [
+ {
+ name: 'mockProfileName',
+ username: 'mockProfileUsername',
+ },
+ {
+ name: 'mockProfileName1',
+ username: 'mockProfileUsername1',
+ },
+ ]
+ const mockProps = [
+ {
+ skipId: 'mockCreatorId',
+ userId: 'mockCreatorId',
+ lastModificationWithin: '24 hours',
+ shortBio: true,
+ },
+ {
+ skipId: 'mockCreatorId1',
+ userId: 'mockCreatorId1',
+ lastModificationWithin: '24 hours',
+ shortBio: true,
+ },
+ ]
+ ;(sqlBuilderModules.renderSql as jest.Mock)
+ .mockReturnValueOnce(mockSearchQuery)
+ .mockReturnValueOnce('usersRenderSql')
+ .mockReturnValueOnce('privateUsersRenderSql')
+ ;(sqlBuilderModules.select as jest.Mock).mockReturnValue('Select')
+ ;(sqlBuilderModules.from as jest.Mock).mockReturnValue('From')
+ ;(mockPg.map as jest.Mock)
+ .mockResolvedValueOnce(mockSearches)
+ .mockResolvedValueOnce(_mockUsers)
+ .mockResolvedValueOnce(_mockPrivateUsers)
+ ;(profileModules.loadProfiles as jest.Mock)
+ .mockResolvedValueOnce({profiles: mockProfiles})
+ .mockResolvedValueOnce({profiles: mockProfiles})
+ jest.spyOn(searchNotificationModules, 'notifyBookmarkedSearch')
+ ;(helperModules.sendSearchAlertsEmail as jest.Mock).mockResolvedValue(null)
- describe('when given valid input', () => {
- it('should send search notification emails', async () => {
- const mockSearchQuery = "mockSqlQuery";
- const mockSearches = [
- {
- created_time: "mockSearchCreatedTime",
- creator_id: "mockCreatorId",
- id: 123,
- last_notified_at: null,
- location: {"mockLocation" : "mockLocationValue"},
- search_filters: null,
- search_name: null,
- },
- {
- created_time: "mockCreatedTime1",
- creator_id: "mockCreatorId1",
- id: 1234,
- last_notified_at: null,
- location: {"mockLocation1" : "mockLocationValue1"},
- search_filters: null,
- search_name: null,
- },
- ];
- const _mockUsers = [
- {
- created_time: "mockUserCreatedTime",
- data: {"mockData" : "mockDataValue"},
- id: "mockId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername",
- },
- {
- created_time: "mockUserCreatedTime1",
- data: {"mockData1" : "mockDataValue1"},
- id: "mockId1",
- name: "mockName1",
- name_username_vector: "mockNameUsernameVector1",
- username: "mockUsername1",
- },
- ];
- const mockUsers = {
- "user1": {
- created_time: "mockUserCreatedTime",
- data: {"mockData" : "mockDataValue"},
- id: "mockId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername",
- },
- "user2": {
- created_time: "mockUserCreatedTime1",
- data: {"mockData1" : "mockDataValue1"},
- id: "mockId1",
- name: "mockName1",
- name_username_vector: "mockNameUsernameVector1",
- username: "mockUsername1",
- },
- };
- const _mockPrivateUsers = [
- {
- data: {"mockData" : "mockDataValue"},
- id: "mockId"
- },
- {
- data: {"mockData1" : "mockDataValue1"},
- id: "mockId1"
- },
- ];
- const mockPrivateUsers = {
- "privateUser1": {
- data: {"mockData" : "mockDataValue"},
- id: "mockId"
- },
- "privateUser2": {
- data: {"mockData1" : "mockDataValue1"},
- id: "mockId1"
- },
- };
- const mockProfiles = [
- {
- name: "mockProfileName",
- username: "mockProfileUsername"
- },
- {
- name: "mockProfileName1",
- username: "mockProfileUsername1"
- },
- ];
- const mockProps = [
- {
- skipId: "mockCreatorId",
- userId: "mockCreatorId",
- lastModificationWithin: '24 hours',
- shortBio: true,
- },
- {
- skipId: "mockCreatorId1",
- userId: "mockCreatorId1",
- lastModificationWithin: '24 hours',
- shortBio: true,
- },
- ];
- (sqlBuilderModules.renderSql as jest.Mock)
- .mockReturnValueOnce(mockSearchQuery)
- .mockReturnValueOnce('usersRenderSql')
- .mockReturnValueOnce('privateUsersRenderSql');
- (sqlBuilderModules.select as jest.Mock).mockReturnValue('Select');
- (sqlBuilderModules.from as jest.Mock).mockReturnValue('From');
- (mockPg.map as jest.Mock)
- .mockResolvedValueOnce(mockSearches)
- .mockResolvedValueOnce(_mockUsers)
- .mockResolvedValueOnce(_mockPrivateUsers);
- (lodashModules.keyBy as jest.Mock)
- .mockReturnValueOnce(mockUsers)
- .mockReturnValueOnce(mockPrivateUsers);
- (profileModules.loadProfiles as jest.Mock)
- .mockResolvedValueOnce({profiles: mockProfiles})
- .mockResolvedValueOnce({profiles: mockProfiles});
- jest.spyOn(searchNotificationModules, 'notifyBookmarkedSearch');
- (helperModules.sendSearchAlertsEmail as jest.Mock).mockResolvedValue(null);
+ const result = await searchNotificationModules.sendSearchNotifications()
- const result = await searchNotificationModules.sendSearchNotifications();
+ expect(result.status).toBe('success')
+ expect(sqlBuilderModules.renderSql).toBeCalledTimes(3)
+ expect(sqlBuilderModules.renderSql).toHaveBeenNthCalledWith(1, 'Select', 'From')
+ expect(sqlBuilderModules.renderSql).toHaveBeenNthCalledWith(2, 'Select', 'From')
+ expect(sqlBuilderModules.renderSql).toHaveBeenNthCalledWith(3, 'Select', 'From')
+ expect(mockPg.map).toBeCalledTimes(3)
+ expect(mockPg.map).toHaveBeenNthCalledWith(1, mockSearchQuery, [], expect.any(Function))
+ expect(mockPg.map).toHaveBeenNthCalledWith(2, 'usersRenderSql', [], expect.any(Function))
+ expect(mockPg.map).toHaveBeenNthCalledWith(
+ 3,
+ 'privateUsersRenderSql',
+ [],
+ expect.any(Function),
+ )
+ expect(profileModules.loadProfiles).toBeCalledTimes(2)
+ expect(profileModules.loadProfiles).toHaveBeenNthCalledWith(1, mockProps[0])
+ expect(profileModules.loadProfiles).toHaveBeenNthCalledWith(2, mockProps[1])
+ expect(searchNotificationModules.notifyBookmarkedSearch).toBeCalledTimes(1)
+ expect(searchNotificationModules.notifyBookmarkedSearch).toBeCalledWith({})
+ })
- expect(result.status).toBe('success');
- expect(sqlBuilderModules.renderSql).toBeCalledTimes(3);
- expect(sqlBuilderModules.renderSql).toHaveBeenNthCalledWith(
- 1,
- 'Select',
- 'From'
- );
- expect(sqlBuilderModules.renderSql).toHaveBeenNthCalledWith(
- 2,
- 'Select',
- 'From'
- );
- expect(sqlBuilderModules.renderSql).toHaveBeenNthCalledWith(
- 3,
- 'Select',
- 'From'
- );
- expect(mockPg.map).toBeCalledTimes(3);
- expect(mockPg.map).toHaveBeenNthCalledWith(
- 1,
- mockSearchQuery,
- [],
- expect.any(Function)
- );
- expect(mockPg.map).toHaveBeenNthCalledWith(
- 2,
- 'usersRenderSql',
- [],
- expect.any(Function)
- );
- expect(mockPg.map).toHaveBeenNthCalledWith(
- 3,
- 'privateUsersRenderSql',
- [],
- expect.any(Function)
- );
- expect(profileModules.loadProfiles).toBeCalledTimes(2);
- expect(profileModules.loadProfiles).toHaveBeenNthCalledWith(
- 1,
- mockProps[0]
- );
- expect(profileModules.loadProfiles).toHaveBeenNthCalledWith(
- 2,
- mockProps[1]
- );
- expect(searchNotificationModules.notifyBookmarkedSearch).toBeCalledTimes(1);
- expect(searchNotificationModules.notifyBookmarkedSearch).toBeCalledWith({});
- });
+ it('should send search notification emails when there is a matching creator_id entry in private users', async () => {
+ const mockSearchQuery = 'mockSqlQuery'
+ const mockSearches = [
+ {
+ created_time: 'mockSearchCreatedTime',
+ creator_id: 'mockCreatorId',
+ id: 123,
+ last_notified_at: null,
+ location: {mockLocation: 'mockLocationValue'},
+ search_filters: null,
+ search_name: null,
+ },
+ {
+ created_time: 'mockCreatedTime1',
+ creator_id: 'mockCreatorId1',
+ id: 1234,
+ last_notified_at: null,
+ location: {mockLocation1: 'mockLocationValue1'},
+ search_filters: null,
+ search_name: null,
+ },
+ ]
+ const _mockUsers = [
+ {
+ created_time: 'mockUserCreatedTime',
+ data: {mockCreatorId: 'mockDataValue'},
+ id: 'mockId',
+ name: 'mockName',
+ name_username_vector: 'mockNameUsernameVector',
+ username: 'mockUsername',
+ },
+ {
+ created_time: 'mockUserCreatedTime1',
+ data: {mockData1: 'mockDataValue1'},
+ id: 'mockId1',
+ name: 'mockName1',
+ name_username_vector: 'mockNameUsernameVector1',
+ username: 'mockUsername1',
+ },
+ ]
+ const _mockPrivateUsers = [
+ {
+ data: {mockData: 'mockDataValue'},
+ id: 'mockCreatorId',
+ },
+ {
+ data: {mockData1: 'mockDataValue1'},
+ id: 'mockId1',
+ },
+ ]
+ const mockProfiles = [
+ {
+ name: 'mockProfileName',
+ username: 'mockProfileUsername',
+ },
+ {
+ name: 'mockProfileName1',
+ username: 'mockProfileUsername1',
+ },
+ ]
+ ;(sqlBuilderModules.renderSql as jest.Mock)
+ .mockReturnValueOnce(mockSearchQuery)
+ .mockReturnValueOnce('usersRenderSql')
+ .mockReturnValueOnce('privateUsersRenderSql')
+ ;(sqlBuilderModules.select as jest.Mock).mockReturnValue('Select')
+ ;(sqlBuilderModules.from as jest.Mock).mockReturnValue('From')
+ ;(mockPg.map as jest.Mock)
+ .mockResolvedValueOnce(mockSearches)
+ .mockResolvedValueOnce(_mockUsers)
+ .mockResolvedValueOnce(_mockPrivateUsers)
+ ;(profileModules.loadProfiles as jest.Mock)
+ .mockResolvedValueOnce({profiles: mockProfiles})
+ .mockResolvedValueOnce({profiles: mockProfiles})
+ jest.spyOn(searchNotificationModules, 'notifyBookmarkedSearch')
+ ;(helperModules.sendSearchAlertsEmail as jest.Mock).mockResolvedValue(null)
- it('should send search notification emails when there is a matching creator_id entry in private users', async () => {
- const mockSearchQuery = "mockSqlQuery";
- const mockSearches = [
- {
- created_time: "mockSearchCreatedTime",
- creator_id: "mockCreatorId",
- id: 123,
- last_notified_at: null,
- location: {"mockLocation" : "mockLocationValue"},
- search_filters: null,
- search_name: null,
- },
- {
- created_time: "mockCreatedTime1",
- creator_id: "mockCreatorId1",
- id: 1234,
- last_notified_at: null,
- location: {"mockLocation1" : "mockLocationValue1"},
- search_filters: null,
- search_name: null,
- },
- ];
- const _mockUsers = [
- {
- created_time: "mockUserCreatedTime",
- data: {"mockData" : "mockDataValue"},
- id: "mockId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername",
- },
- {
- created_time: "mockUserCreatedTime1",
- data: {"mockData1" : "mockDataValue1"},
- id: "mockId1",
- name: "mockName1",
- name_username_vector: "mockNameUsernameVector1",
- username: "mockUsername1",
- },
- ];
- const mockUsers = {
- "user1": {
- created_time: "mockUserCreatedTime",
- data: {"mockData" : "mockDataValue"},
- id: "mockId",
- name: "mockName",
- name_username_vector: "mockNameUsernameVector",
- username: "mockUsername",
- },
- "user2": {
- created_time: "mockUserCreatedTime1",
- data: {"mockData1" : "mockDataValue1"},
- id: "mockId1",
- name: "mockName1",
- name_username_vector: "mockNameUsernameVector1",
- username: "mockUsername1",
- },
- };
- const _mockPrivateUsers = [
- {
- data: {"mockData" : "mockDataValue"},
- id: "mockId"
- },
- {
- data: {"mockData1" : "mockDataValue1"},
- id: "mockId1"
- },
- ];
- const mockPrivateUsers = {
- "mockCreatorId": {
- data: {"mockData" : "mockDataValue"},
- id: "mockId"
- },
- "mockCreatorId1": {
- data: {"mockData1" : "mockDataValue1"},
- id: "mockId1"
- },
- };
- const mockProfiles = [
- {
- name: "mockProfileName",
- username: "mockProfileUsername"
- },
- {
- name: "mockProfileName1",
- username: "mockProfileUsername1"
- },
- ];
- (sqlBuilderModules.renderSql as jest.Mock)
- .mockReturnValueOnce(mockSearchQuery)
- .mockReturnValueOnce('usersRenderSql')
- .mockReturnValueOnce('privateUsersRenderSql');
- (sqlBuilderModules.select as jest.Mock).mockReturnValue('Select');
- (sqlBuilderModules.from as jest.Mock).mockReturnValue('From');
- (mockPg.map as jest.Mock)
- .mockResolvedValueOnce(mockSearches)
- .mockResolvedValueOnce(_mockUsers)
- .mockResolvedValueOnce(_mockPrivateUsers);
- (lodashModules.keyBy as jest.Mock)
- .mockReturnValueOnce(mockUsers)
- .mockReturnValueOnce(mockPrivateUsers);
- (profileModules.loadProfiles as jest.Mock)
- .mockResolvedValueOnce({profiles: mockProfiles})
- .mockResolvedValueOnce({profiles: mockProfiles});
- jest.spyOn(searchNotificationModules, 'notifyBookmarkedSearch');
- (helperModules.sendSearchAlertsEmail as jest.Mock).mockResolvedValue(null);
+ await searchNotificationModules.sendSearchNotifications()
- await searchNotificationModules.sendSearchNotifications();
-
- expect(searchNotificationModules.notifyBookmarkedSearch).toBeCalledTimes(1);
- expect(searchNotificationModules.notifyBookmarkedSearch).not.toBeCalledWith({});
-
- });
- });
-});
\ No newline at end of file
+ expect(searchNotificationModules.notifyBookmarkedSearch).toBeCalledTimes(1)
+ expect(searchNotificationModules.notifyBookmarkedSearch).not.toBeCalledWith({})
+ })
+ })
+})
diff --git a/backend/api/tests/unit/set-compatibility-answers.unit.test.ts b/backend/api/tests/unit/set-compatibility-answers.unit.test.ts
index e2e13325..569b78a0 100644
--- a/backend/api/tests/unit/set-compatibility-answers.unit.test.ts
+++ b/backend/api/tests/unit/set-compatibility-answers.unit.test.ts
@@ -1,75 +1,71 @@
-jest.mock('shared/supabase/init');
-jest.mock('shared/compatibility/compute-scores');
+jest.mock('shared/supabase/init')
+jest.mock('shared/compatibility/compute-scores')
-import {sqlMatch} from "common/test-utils";
-import {setCompatibilityAnswer} from "api/set-compatibility-answer";
-import * as supabaseInit from "shared/supabase/init";
-import {recomputeCompatibilityScoresForUser} from "shared/compatibility/compute-scores";
-import {AuthedUser} from "api/helpers/endpoint";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {setCompatibilityAnswer} from 'api/set-compatibility-answer'
+import {sqlMatch} from 'common/test-utils'
+import {recomputeCompatibilityScoresForUser} from 'shared/compatibility/compute-scores'
+import * as supabaseInit from 'shared/supabase/init'
describe('setCompatibilityAnswer', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- one: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ one: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should set compatibility answers', async () => {
+ const mockProps = {
+ questionId: 1,
+ multipleChoice: 2,
+ prefChoices: [1, 2, 3, 4, 5],
+ importance: 1,
+ explanation: 'mockExplanation',
+ }
+ const mockResult = {
+ created_time: 'mockCreatedTime',
+ creator_id: 'mockCreatorId',
+ explanation: 'mockExplanation',
+ id: 123,
+ importance: 1,
+ multipleChoice: 2,
+ prefChoices: [1, 2, 3, 4, 5],
+ questionId: 1,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- describe('when given valid input', () => {
- it('should set compatibility answers', async () => {
- const mockProps = {
- questionId: 1,
- multipleChoice: 2,
- prefChoices: [1,2,3,4,5],
- importance: 1,
- explanation: "mockExplanation"
- };
- const mockResult = {
- created_time: "mockCreatedTime",
- creator_id: "mockCreatorId",
- explanation: "mockExplanation",
- id: 123,
- importance: 1,
- multipleChoice: 2,
- prefChoices: [1,2,3,4,5],
- questionId: 1,
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(mockPg.one as jest.Mock).mockResolvedValue(mockResult)
+ ;(recomputeCompatibilityScoresForUser as jest.Mock).mockResolvedValue(null)
- (mockPg.one as jest.Mock).mockResolvedValue(mockResult);
- (recomputeCompatibilityScoresForUser as jest.Mock).mockResolvedValue(null);
+ const result: any = await setCompatibilityAnswer(mockProps, mockAuth, mockReq)
- const result: any = await setCompatibilityAnswer(mockProps, mockAuth, mockReq);
+ expect(result.result).toBe(mockResult)
+ expect(mockPg.one).toBeCalledTimes(1)
+ expect(mockPg.one).toBeCalledWith({
+ text: sqlMatch('INSERT INTO compatibility_answers'),
+ values: [
+ mockAuth.uid,
+ mockProps.questionId,
+ mockProps.multipleChoice,
+ mockProps.prefChoices,
+ mockProps.importance,
+ mockProps.explanation,
+ ],
+ })
- expect(result.result).toBe(mockResult);
- expect(mockPg.one).toBeCalledTimes(1);
- expect(mockPg.one).toBeCalledWith(
- {
- text: sqlMatch('INSERT INTO compatibility_answers'),
- values: [
- mockAuth.uid,
- mockProps.questionId,
- mockProps.multipleChoice,
- mockProps.prefChoices,
- mockProps.importance,
- mockProps.explanation,
- ]
- }
- );
+ await result.continue()
- await result.continue();
-
- expect(recomputeCompatibilityScoresForUser).toBeCalledTimes(1);
- expect(recomputeCompatibilityScoresForUser).toBeCalledWith(mockAuth.uid, expect.any(Object));
- });
- });
-});
\ No newline at end of file
+ expect(recomputeCompatibilityScoresForUser).toBeCalledTimes(1)
+ expect(recomputeCompatibilityScoresForUser).toBeCalledWith(mockAuth.uid, expect.any(Object))
+ })
+ })
+})
diff --git a/backend/api/tests/unit/set-last-online-time.unit.test.ts b/backend/api/tests/unit/set-last-online-time.unit.test.ts
index 36806db6..bcb82a2c 100644
--- a/backend/api/tests/unit/set-last-online-time.unit.test.ts
+++ b/backend/api/tests/unit/set-last-online-time.unit.test.ts
@@ -1,56 +1,55 @@
-jest.mock('shared/supabase/init');
+jest.mock('shared/supabase/init')
-import { AuthedUser } from "api/helpers/endpoint";
-import * as setLastTimeOnlineModule from "api/set-last-online-time";
-import * as supabaseInit from "shared/supabase/init";
+import {AuthedUser} from 'api/helpers/endpoint'
+import * as setLastTimeOnlineModule from 'api/set-last-online-time'
+import * as supabaseInit from 'shared/supabase/init'
describe('setLastOnlineTimeUser', () => {
- let mockPg: any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- none: jest.fn(),
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg: any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should change the users last online time', async () => {
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockProps = {} as any;
-
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- jest.spyOn(setLastTimeOnlineModule, 'setLastOnlineTimeUser');
+ describe('when given valid input', () => {
+ it('should change the users last online time', async () => {
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockProps = {} as any
- await setLastTimeOnlineModule.setLastOnlineTime(mockProps, mockAuth, mockReq);
- const [query, userId] = mockPg.none.mock.calls[0];
-
- expect(setLastTimeOnlineModule.setLastOnlineTimeUser).toBeCalledTimes(1);
- expect(setLastTimeOnlineModule.setLastOnlineTimeUser).toBeCalledWith(mockAuth.uid);
- expect(mockPg.none).toBeCalledTimes(1);
- expect(userId).toContain(mockAuth.uid);
- expect(query).toContain("VALUES ($1, now())");
- expect(query).toContain("ON CONFLICT (user_id)");
- expect(query).toContain("DO UPDATE");
- expect(query).toContain("user_activity.last_online_time < now() - interval '1 minute'");
- });
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ jest.spyOn(setLastTimeOnlineModule, 'setLastOnlineTimeUser')
- it('should return if there is no auth', async () => {
- const mockAuth = { } as any;
- const mockReq = {} as any;
- const mockProps = {} as any;
-
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- jest.spyOn(setLastTimeOnlineModule, 'setLastOnlineTimeUser');
+ await setLastTimeOnlineModule.setLastOnlineTime(mockProps, mockAuth, mockReq)
+ const [query, userId] = mockPg.none.mock.calls[0]
- await setLastTimeOnlineModule.setLastOnlineTime(mockProps, mockAuth, mockReq);
-
- expect(setLastTimeOnlineModule.setLastOnlineTimeUser).not.toBeCalled();
- });
- });
-});
\ No newline at end of file
+ expect(setLastTimeOnlineModule.setLastOnlineTimeUser).toBeCalledTimes(1)
+ expect(setLastTimeOnlineModule.setLastOnlineTimeUser).toBeCalledWith(mockAuth.uid)
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(userId).toContain(mockAuth.uid)
+ expect(query).toContain('VALUES ($1, now())')
+ expect(query).toContain('ON CONFLICT (user_id)')
+ expect(query).toContain('DO UPDATE')
+ expect(query).toContain("user_activity.last_online_time < now() - interval '1 minute'")
+ })
+
+ it('should return if there is no auth', async () => {
+ const mockAuth = {} as any
+ const mockReq = {} as any
+ const mockProps = {} as any
+
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ jest.spyOn(setLastTimeOnlineModule, 'setLastOnlineTimeUser')
+
+ await setLastTimeOnlineModule.setLastOnlineTime(mockProps, mockAuth, mockReq)
+
+ expect(setLastTimeOnlineModule.setLastOnlineTimeUser).not.toBeCalled()
+ })
+ })
+})
diff --git a/backend/api/tests/unit/ship-profiles.unit.test.ts b/backend/api/tests/unit/ship-profiles.unit.test.ts
index 186ed407..84baf713 100644
--- a/backend/api/tests/unit/ship-profiles.unit.test.ts
+++ b/backend/api/tests/unit/ship-profiles.unit.test.ts
@@ -1,228 +1,217 @@
-jest.mock('shared/supabase/init');
-jest.mock('common/util/try-catch');
-jest.mock('shared/supabase/utils');
-jest.mock('shared/create-profile-notification');
+jest.mock('shared/supabase/init')
+jest.mock('common/util/try-catch')
+jest.mock('shared/supabase/utils')
+jest.mock('shared/create-profile-notification')
-import {shipProfiles} from "api/ship-profiles";
-import * as supabaseInit from "shared/supabase/init";
-import {tryCatch} from "common/util/try-catch";
-import * as supabaseUtils from "shared/supabase/utils";
-import * as profileNotificationModules from "shared/create-profile-notification";
-import {AuthedUser} from "api/helpers/endpoint";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {shipProfiles} from 'api/ship-profiles'
import {sqlMatch} from 'common/test-utils'
+import {tryCatch} from 'common/util/try-catch'
+import * as profileNotificationModules from 'shared/create-profile-notification'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseUtils from 'shared/supabase/utils'
describe('shipProfiles', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- none: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should return success if the profile ship already exists', async () => {
+ const mockProps = {
+ targetUserId1: 'mockTargetUserId1',
+ targetUserId2: 'mockTargetUserId2',
+ remove: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockExisting = {
+ data: {ship_id: 'mockShipId'},
+ error: null,
+ }
- describe('when given valid input', () => {
- it('should return success if the profile ship already exists', async () => {
- const mockProps = {
- targetUserId1: "mockTargetUserId1",
- targetUserId2: "mockTargetUserId2",
- remove: false,
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockExisting = {
- data: { ship_id : "mockShipId" },
- error: null
- };
+ ;(tryCatch as jest.Mock).mockResolvedValue(mockExisting)
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
- (tryCatch as jest.Mock).mockResolvedValue(mockExisting);
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
+ const result: any = await shipProfiles(mockProps, mockAuth, mockReq)
- const result: any = await shipProfiles(mockProps, mockAuth, mockReq);
+ expect(result.status).toBe('success')
+ expect(tryCatch).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(sqlMatch('select ship_id from profile_ships'), [
+ mockAuth.uid,
+ mockProps.targetUserId1,
+ mockProps.targetUserId2,
+ ])
+ })
- expect(result.status).toBe('success');
- expect(tryCatch).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select ship_id from profile_ships'),
- [mockAuth.uid, mockProps.targetUserId1, mockProps.targetUserId2]
- );
- });
+ it('should return success if trying to remove a profile ship that already exists', async () => {
+ const mockProps = {
+ targetUserId1: 'mockTargetUserId1',
+ targetUserId2: 'mockTargetUserId2',
+ remove: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockExisting = {
+ data: {ship_id: 'mockShipId'},
+ error: null,
+ }
- it('should return success if trying to remove a profile ship that already exists', async () => {
- const mockProps = {
- targetUserId1: "mockTargetUserId1",
- targetUserId2: "mockTargetUserId2",
- remove: true,
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockExisting = {
- data: { ship_id : "mockShipId" },
- error: null
- };
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce(mockExisting)
+ .mockResolvedValueOnce({error: null})
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce(mockExisting)
- .mockResolvedValueOnce({error: null});
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
+ const result: any = await shipProfiles(mockProps, mockAuth, mockReq)
- const result: any = await shipProfiles(mockProps, mockAuth, mockReq);
+ expect(result.status).toBe('success')
+ expect(tryCatch).toBeCalledTimes(2)
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(sqlMatch('select ship_id from profile_ships'), [
+ mockAuth.uid,
+ mockProps.targetUserId1,
+ mockProps.targetUserId2,
+ ])
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(sqlMatch('delete from profile_ships where ship_id = $1'), [
+ mockExisting.data.ship_id,
+ ])
+ })
- expect(result.status).toBe('success');
- expect(tryCatch).toBeCalledTimes(2);
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select ship_id from profile_ships'),
- [mockAuth.uid, mockProps.targetUserId1, mockProps.targetUserId2]
- );
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('delete from profile_ships where ship_id = $1'),
- [mockExisting.data.ship_id]
- );
- });
+ it('should return success when creating a new profile ship', async () => {
+ const mockProps = {
+ targetUserId1: 'mockTargetUserId1',
+ targetUserId2: 'mockTargetUserId2',
+ remove: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockExisting = {
+ data: null,
+ error: null,
+ }
+ const mockData = {
+ created_time: 'mockCreatedTime',
+ creator_id: 'mockCreatorId',
+ ship_id: 'mockShipId',
+ target1_id: 'mockTarget1Id',
+ target2_id: 'mockTarget2Id',
+ }
- it('should return success when creating a new profile ship', async () => {
- const mockProps = {
- targetUserId1: "mockTargetUserId1",
- targetUserId2: "mockTargetUserId2",
- remove: false,
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockExisting = {
- data: null,
- error: null
- };
- const mockData = {
- created_time: "mockCreatedTime",
- creator_id: "mockCreatorId",
- ship_id: "mockShipId",
- target1_id: "mockTarget1Id",
- target2_id: "mockTarget2Id",
- };
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce(mockExisting)
+ .mockResolvedValueOnce({data: mockData, error: null})
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
+ ;(supabaseUtils.insert as jest.Mock).mockReturnValue(null)
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce(mockExisting)
- .mockResolvedValueOnce({data: mockData, error: null});
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
- (supabaseUtils.insert as jest.Mock).mockReturnValue(null);
+ const result: any = await shipProfiles(mockProps, mockAuth, mockReq)
- const result: any = await shipProfiles(mockProps, mockAuth, mockReq);
+ expect(result.result.status).toBe('success')
+ expect(tryCatch).toBeCalledTimes(2)
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(sqlMatch('select ship_id from profile_ships'), [
+ mockAuth.uid,
+ mockProps.targetUserId1,
+ mockProps.targetUserId2,
+ ])
+ expect(supabaseUtils.insert).toBeCalledTimes(1)
+ expect(supabaseUtils.insert).toBeCalledWith(expect.any(Object), 'profile_ships', {
+ creator_id: mockAuth.uid,
+ target1_id: mockProps.targetUserId1,
+ target2_id: mockProps.targetUserId2,
+ })
+ ;(profileNotificationModules.createProfileShipNotification as jest.Mock).mockReturnValue(null)
- expect(result.result.status).toBe('success');
- expect(tryCatch).toBeCalledTimes(2);
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select ship_id from profile_ships'),
- [mockAuth.uid, mockProps.targetUserId1, mockProps.targetUserId2]
- );
- expect(supabaseUtils.insert).toBeCalledTimes(1);
- expect(supabaseUtils.insert).toBeCalledWith(
- expect.any(Object),
- 'profile_ships',
- {
- creator_id: mockAuth.uid,
- target1_id: mockProps.targetUserId1,
- target2_id: mockProps.targetUserId2,
- }
- );
+ await result.continue()
- (profileNotificationModules.createProfileShipNotification as jest.Mock).mockReturnValue(null);
+ expect(profileNotificationModules.createProfileShipNotification).toBeCalledTimes(2)
+ expect(profileNotificationModules.createProfileShipNotification).toHaveBeenNthCalledWith(
+ 1,
+ mockData,
+ mockData.target1_id,
+ )
+ expect(profileNotificationModules.createProfileShipNotification).toHaveBeenNthCalledWith(
+ 2,
+ mockData,
+ mockData.target2_id,
+ )
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if unable to check ship', async () => {
+ const mockProps = {
+ targetUserId1: 'mockTargetUserId1',
+ targetUserId2: 'mockTargetUserId2',
+ remove: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockExisting = {
+ data: null,
+ error: Error,
+ }
- await result.continue();
+ ;(tryCatch as jest.Mock).mockResolvedValue(mockExisting)
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
- expect(profileNotificationModules.createProfileShipNotification).toBeCalledTimes(2);
- expect(profileNotificationModules.createProfileShipNotification).toHaveBeenNthCalledWith(
- 1,
- mockData,
- mockData.target1_id
- );
- expect(profileNotificationModules.createProfileShipNotification).toHaveBeenNthCalledWith(
- 2,
- mockData,
- mockData.target2_id
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if unable to check ship', async () => {
- const mockProps = {
- targetUserId1: "mockTargetUserId1",
- targetUserId2: "mockTargetUserId2",
- remove: false,
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockExisting = {
- data: null,
- error: Error
- };
+ expect(shipProfiles(mockProps, mockAuth, mockReq)).rejects.toThrow(
+ 'Error when checking ship: ',
+ )
+ })
- (tryCatch as jest.Mock).mockResolvedValue(mockExisting);
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
+ it('should throw if unable to remove a profile ship that already exists', async () => {
+ const mockProps = {
+ targetUserId1: 'mockTargetUserId1',
+ targetUserId2: 'mockTargetUserId2',
+ remove: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockExisting = {
+ data: {ship_id: 'mockShipId'},
+ error: null,
+ }
- expect(shipProfiles(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Error when checking ship: ');
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce(mockExisting)
+ .mockResolvedValueOnce({error: Error})
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
- });
+ expect(shipProfiles(mockProps, mockAuth, mockReq)).rejects.toThrow('Failed to remove ship: ')
+ })
- it('should throw if unable to remove a profile ship that already exists', async () => {
- const mockProps = {
- targetUserId1: "mockTargetUserId1",
- targetUserId2: "mockTargetUserId2",
- remove: true,
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockExisting = {
- data: { ship_id : "mockShipId" },
- error: null
- };
+ it('should return success when creating a new profile ship', async () => {
+ const mockProps = {
+ targetUserId1: 'mockTargetUserId1',
+ targetUserId2: 'mockTargetUserId2',
+ remove: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockExisting = {
+ data: null,
+ error: null,
+ }
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce(mockExisting)
- .mockResolvedValueOnce({error: Error});
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
- (mockPg.none as jest.Mock).mockResolvedValue(null);
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce(mockExisting)
+ .mockResolvedValueOnce({data: null, error: Error})
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
+ ;(supabaseUtils.insert as jest.Mock).mockReturnValue(null)
- expect(shipProfiles(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Failed to remove ship: ');
- });
-
- it('should return success when creating a new profile ship', async () => {
- const mockProps = {
- targetUserId1: "mockTargetUserId1",
- targetUserId2: "mockTargetUserId2",
- remove: false,
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockExisting = {
- data: null,
- error: null
- };
-
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce(mockExisting)
- .mockResolvedValueOnce({data: null, error: Error});
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
- (supabaseUtils.insert as jest.Mock).mockReturnValue(null);
-
- expect(shipProfiles(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Failed to create ship: ');
-
- });
- });
-});
\ No newline at end of file
+ expect(shipProfiles(mockProps, mockAuth, mockReq)).rejects.toThrow('Failed to create ship: ')
+ })
+ })
+})
diff --git a/backend/api/tests/unit/star-profile.unit.test.ts b/backend/api/tests/unit/star-profile.unit.test.ts
index 911c36d8..f44d3171 100644
--- a/backend/api/tests/unit/star-profile.unit.test.ts
+++ b/backend/api/tests/unit/star-profile.unit.test.ts
@@ -1,147 +1,136 @@
-import {sqlMatch} from "common/test-utils";
-import {AuthedUser} from "api/helpers/endpoint";
-import {starProfile} from "api/star-profile";
-import {tryCatch} from "common/util/try-catch";
-import * as supabaseInit from "shared/supabase/init";
-import * as supabaseUtils from "shared/supabase/utils";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {starProfile} from 'api/star-profile'
+import {sqlMatch} from 'common/test-utils'
+import {tryCatch} from 'common/util/try-catch'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseUtils from 'shared/supabase/utils'
-jest.mock('common/util/try-catch');
-jest.mock('shared/supabase/init');
-jest.mock('shared/supabase/utils');
+jest.mock('common/util/try-catch')
+jest.mock('shared/supabase/init')
+jest.mock('shared/supabase/utils')
describe('startProfile', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- none: jest.fn(),
- oneOrNone: jest.fn(),
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ none: jest.fn(),
+ oneOrNone: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should return success when trying to star a profile for the first time', async () => {
+ const mockProps = {
+ targetUserId: 'mockTargetUserId',
+ remove: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- describe('when given valid input', () => {
- it('should return success when trying to star a profile for the first time', async () => {
- const mockProps = {
- targetUserId: "mockTargetUserId",
- remove: false
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce({data: null})
+ .mockResolvedValueOnce({error: null})
+ ;(supabaseUtils.insert as jest.Mock).mockReturnValue(null)
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce({data: null})
- .mockResolvedValueOnce({error: null});
- (supabaseUtils.insert as jest.Mock).mockReturnValue(null);
+ const result: any = await starProfile(mockProps, mockAuth, mockReq)
- const result: any = await starProfile(mockProps, mockAuth, mockReq);
+ expect(result.status).toBe('success')
+ expect(tryCatch).toBeCalledTimes(2)
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(
+ sqlMatch('select * from profile_stars where creator_id = $1 and target_id = $2'),
+ [mockAuth.uid, mockProps.targetUserId],
+ )
+ expect(supabaseUtils.insert).toBeCalledTimes(1)
+ expect(supabaseUtils.insert).toBeCalledWith(expect.any(Object), 'profile_stars', {
+ creator_id: mockAuth.uid,
+ target_id: mockProps.targetUserId,
+ })
+ })
- expect(result.status).toBe('success');
- expect(tryCatch).toBeCalledTimes(2);
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select * from profile_stars where creator_id = $1 and target_id = $2'),
- [mockAuth.uid, mockProps.targetUserId]
- );
- expect(supabaseUtils.insert).toBeCalledTimes(1);
- expect(supabaseUtils.insert).toBeCalledWith(
- expect.any(Object),
- 'profile_stars',
- {
- creator_id: mockAuth.uid,
- target_id: mockProps.targetUserId
- }
- );
- });
+ it('should return success if the profile already has a star', async () => {
+ const mockProps = {
+ targetUserId: 'mockTargetUserId',
+ remove: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockExisting = {
+ created_time: 'mockCreatedTime',
+ creator_id: 'mockCreatorId',
+ star_id: 'mockStarId',
+ target_id: 'mockTarget',
+ }
- it('should return success if the profile already has a star', async () => {
- const mockProps = {
- targetUserId: "mockTargetUserId",
- remove: false
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockExisting = {
- created_time: "mockCreatedTime",
- creator_id: "mockCreatorId",
- star_id: "mockStarId",
- target_id: "mockTarget",
- };
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: mockExisting})
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({data: mockExisting});
+ const result: any = await starProfile(mockProps, mockAuth, mockReq)
- const result: any = await starProfile(mockProps, mockAuth, mockReq);
+ expect(result.status).toBe('success')
+ expect(tryCatch).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(supabaseUtils.insert).not.toBeCalledTimes(1)
+ })
- expect(result.status).toBe('success');
- expect(tryCatch).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(supabaseUtils.insert).not.toBeCalledTimes(1);
- });
+ it('should return success when trying to remove a star', async () => {
+ const mockProps = {
+ targetUserId: 'mockTargetUserId',
+ remove: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- it('should return success when trying to remove a star', async () => {
- const mockProps = {
- targetUserId: "mockTargetUserId",
- remove: true
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValue({error: null})
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValue({error: null});
+ const result: any = await starProfile(mockProps, mockAuth, mockReq)
- const result: any = await starProfile(mockProps, mockAuth, mockReq);
+ expect(result.status).toBe('success')
+ expect(tryCatch).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(
+ sqlMatch('delete from profile_stars where creator_id = $1 and target_id = $2'),
+ [mockAuth.uid, mockProps.targetUserId],
+ )
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if unable to remove star', async () => {
+ const mockProps = {
+ targetUserId: 'mockTargetUserId',
+ remove: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- expect(result.status).toBe('success');
- expect(tryCatch).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('delete from profile_stars where creator_id = $1 and target_id = $2'),
- [mockAuth.uid, mockProps.targetUserId]
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if unable to remove star', async () => {
- const mockProps = {
- targetUserId: "mockTargetUserId",
- remove: true
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock).mockResolvedValueOnce({error: Error})
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock).mockResolvedValueOnce({error: Error});
+ expect(starProfile(mockProps, mockAuth, mockReq)).rejects.toThrow('Failed to remove star')
+ })
- expect(starProfile(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Failed to remove star');
- });
+ it('should throw if unable to add a star', async () => {
+ const mockProps = {
+ targetUserId: 'mockTargetUserId',
+ remove: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- it('should throw if unable to add a star', async () => {
- const mockProps = {
- targetUserId: "mockTargetUserId",
- remove: false
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(null)
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce({data: null})
+ .mockResolvedValueOnce({error: Error})
+ ;(supabaseUtils.insert as jest.Mock).mockReturnValue(null)
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(null);
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce({data: null})
- .mockResolvedValueOnce({error: Error});
- (supabaseUtils.insert as jest.Mock).mockReturnValue(null);
-
- expect(starProfile(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Failed to add star');
-
- });
- });
-});
\ No newline at end of file
+ expect(starProfile(mockProps, mockAuth, mockReq)).rejects.toThrow('Failed to add star')
+ })
+ })
+})
diff --git a/backend/api/tests/unit/update-me.unit.test.ts b/backend/api/tests/unit/update-me.unit.test.ts
index 838ca2bb..579495d7 100644
--- a/backend/api/tests/unit/update-me.unit.test.ts
+++ b/backend/api/tests/unit/update-me.unit.test.ts
@@ -1,235 +1,158 @@
-jest.mock('common/api/user-types');
-jest.mock('common/util/clean-username');
-jest.mock('shared/supabase/init');
-jest.mock('common/util/object');
-jest.mock('lodash');
-jest.mock('shared/utils');
-jest.mock('shared/supabase/users');
-jest.mock('shared/websockets/helpers');
-jest.mock('common/envs/constants');
+jest.mock('shared/supabase/init')
+jest.mock('shared/utils')
+jest.mock('shared/supabase/users')
+jest.mock('shared/websockets/helpers')
-import {updateMe} from "api/update-me";
-import {toUserAPIResponse} from "common/api/user-types";
-import * as cleanUsernameModules from "common/util/clean-username";
-import * as supabaseInit from "shared/supabase/init";
-import * as objectUtils from "common/util/object";
-import * as lodashModules from "lodash";
-import * as sharedUtils from "shared/utils";
-import * as supabaseUsers from "shared/supabase/users";
-import * as websocketHelperModules from "shared/websockets/helpers";
-import {AuthedUser} from "api/helpers/endpoint";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {updateMe} from 'api/update-me'
import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseUsers from 'shared/supabase/users'
+import * as sharedUtils from 'shared/utils'
+import * as websocketHelperModules from 'shared/websockets/helpers'
describe('updateMe', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- none: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should update user information', async () => {
+ const mockProps = {
+ name: 'mockName',
+ username: 'mockUsername',
+ avatarUrl: 'mockAvatarUrl',
+ link: {mockLink: 'mockLinkValue'},
+ } as any
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockData = {link: mockProps.link}
- describe('when given valid input', () => {
- it('should update user information', async () => {
- const mockProps = {} as any;
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockUpdate = {
- name: "mockName",
- username: "mockUsername",
- avatarUrl: "mockAvatarUrl",
- link: {"mockLink" : "mockLinkValue"},
- };
- const mockStripped = {
- bio: "mockBio"
- };
- const mockData = {link: "mockNewLinks"};
- const arrySpy = jest.spyOn(Array.prototype, 'includes');
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(true)
+ ;(sharedUtils.getUserByUsername as jest.Mock).mockReturnValue(false)
+ ;(supabaseUsers.updateUser as jest.Mock)
+ .mockResolvedValueOnce(null)
+ .mockResolvedValueOnce(null)
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockData)
+ ;(mockPg.none as jest.Mock).mockResolvedValueOnce(null).mockResolvedValueOnce(null)
+ ;(websocketHelperModules.broadcastUpdatedUser as jest.Mock).mockReturnValue(null)
- (lodashModules.cloneDeep as jest.Mock).mockReturnValue(mockUpdate);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(true);
- (cleanUsernameModules.cleanDisplayName as jest.Mock).mockReturnValue(mockUpdate.name);
- (cleanUsernameModules.cleanUsername as jest.Mock).mockReturnValue(mockUpdate.username);
- arrySpy.mockReturnValue(false);
- (sharedUtils.getUserByUsername as jest.Mock).mockReturnValue(false);
- (supabaseUsers.updateUser as jest.Mock)
- .mockResolvedValueOnce(null)
- .mockResolvedValueOnce(null);
- (lodashModules.mapValues as jest.Mock).mockReturnValue(mockStripped);
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockData);
- (mockPg.none as jest.Mock)
- .mockResolvedValueOnce(null)
- .mockResolvedValueOnce(null);
- (objectUtils.removeUndefinedProps as jest.Mock).mockReturnValue("mockRemoveUndefinedProps");
- (websocketHelperModules.broadcastUpdatedUser as jest.Mock).mockReturnValue(null);
- (toUserAPIResponse as jest.Mock).mockReturnValue(null);
+ await updateMe(mockProps, mockAuth, mockReq)
- await updateMe(mockProps, mockAuth, mockReq);
-
- expect(lodashModules.cloneDeep).toBeCalledTimes(1);
- expect(lodashModules.cloneDeep).toBeCalledWith(mockProps);
- expect(sharedUtils.getUser).toBeCalledTimes(1);
- expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid);
- expect(cleanUsernameModules.cleanDisplayName).toBeCalledTimes(1);
- expect(cleanUsernameModules.cleanDisplayName).toBeCalledWith(mockUpdate.name);
- expect(cleanUsernameModules.cleanUsername).toBeCalledTimes(1);
- expect(cleanUsernameModules.cleanUsername).toBeCalledWith(mockUpdate.username);
- expect(arrySpy).toBeCalledTimes(1);
- expect(arrySpy).toBeCalledWith(mockUpdate.username);
- expect(sharedUtils.getUserByUsername).toBeCalledTimes(1);
- expect(sharedUtils.getUserByUsername).toBeCalledWith(mockUpdate.username);
- expect(supabaseUsers.updateUser).toBeCalledTimes(2);
- expect(supabaseUsers.updateUser).toHaveBeenNthCalledWith(
- 1,
- expect.any(Object),
- mockAuth.uid,
- 'mockRemoveUndefinedProps'
- );
- expect(supabaseUsers.updateUser).toHaveBeenNthCalledWith(
- 2,
- expect.any(Object),
- mockAuth.uid,
- {avatarUrl: mockUpdate.avatarUrl}
- );
- expect(lodashModules.mapValues).toBeCalledTimes(1);
- expect(lodashModules.mapValues).toBeCalledWith(
- expect.any(Object),
- expect.any(Function)
- );
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('update users'),
- {
- adds: expect.any(Object),
- removes: expect.any(Array),
- id: mockAuth.uid
- }
- );
- expect(mockPg.none).toBeCalledTimes(2);
- expect(mockPg.none).toHaveBeenNthCalledWith(
- 1,
- sqlMatch(`update users
+ expect(sharedUtils.getUser).toBeCalledTimes(1)
+ expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid)
+ expect(sharedUtils.getUserByUsername).toBeCalledTimes(1)
+ expect(sharedUtils.getUserByUsername).toBeCalledWith(mockProps.username)
+ expect(supabaseUsers.updateUser).toBeCalledTimes(2)
+ expect(supabaseUsers.updateUser).toHaveBeenNthCalledWith(
+ 1,
+ expect.any(Object),
+ mockAuth.uid,
+ {},
+ )
+ expect(supabaseUsers.updateUser).toHaveBeenNthCalledWith(
+ 2,
+ expect.any(Object),
+ mockAuth.uid,
+ {avatarUrl: mockProps.avatarUrl},
+ )
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(sqlMatch('update users'), {
+ adds: expect.any(Object),
+ removes: expect.any(Array),
+ id: mockAuth.uid,
+ })
+ expect(mockPg.none).toBeCalledTimes(2)
+ expect(mockPg.none).toHaveBeenNthCalledWith(
+ 1,
+ sqlMatch(`update users
set name = $1
where id = $2`),
- [mockUpdate.name, mockAuth.uid]
- );
- expect(mockPg.none).toHaveBeenNthCalledWith(
- 2,
- sqlMatch(`update users
+ [mockProps.name, mockAuth.uid],
+ )
+ expect(mockPg.none).toHaveBeenNthCalledWith(
+ 2,
+ sqlMatch(`update users
set username = $1
where id = $2`),
- [mockUpdate.username, mockAuth.uid]
- );
- expect(objectUtils.removeUndefinedProps).toBeCalledTimes(2);
- expect(objectUtils.removeUndefinedProps).toHaveBeenNthCalledWith(
- 2,
- {
- id: mockAuth.uid,
- name: mockUpdate.name,
- username: mockUpdate.username,
- avatarUrl: mockUpdate.avatarUrl,
- link: mockData.link
- }
- );
- expect(websocketHelperModules.broadcastUpdatedUser).toBeCalledTimes(1);
- expect(websocketHelperModules.broadcastUpdatedUser).toBeCalledWith('mockRemoveUndefinedProps');
- expect(toUserAPIResponse).toBeCalledTimes(1);
- });
- });
- describe('when an error occurs', () => {
- it('should throw if no account was found', async () => {
- const mockProps = {} as any;
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockUpdate = {
- name: "mockName",
- username: "mockUsername",
- avatarUrl: "mockAvatarUrl",
- link: {"mockLink" : "mockLinkValue"},
- };
+ [mockProps.username, mockAuth.uid],
+ )
+ expect(websocketHelperModules.broadcastUpdatedUser).toBeCalledTimes(1)
+ expect(websocketHelperModules.broadcastUpdatedUser).toBeCalledWith({
+ ...mockProps,
+ id: mockAuth.uid,
+ })
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if no account was found', async () => {
+ const mockProps = {} as any
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (lodashModules.cloneDeep as jest.Mock).mockReturnValue(mockUpdate);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
- expect(updateMe(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Your account was not found');
- });
+ expect(updateMe(mockProps, mockAuth, mockReq)).rejects.toThrow('Your account was not found')
+ })
- it('should throw if the username is invalid', async () => {
- const mockProps = {} as any;
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockUpdate = {
- name: "mockName",
- username: "mockUsername",
- avatarUrl: "mockAvatarUrl",
- link: {"mockLink" : "mockLinkValue"},
- };
+ it('should throw if the username is invalid', async () => {
+ const mockProps = {
+ name: 'mockName',
+ username: ';#',
+ avatarUrl: 'mockAvatarUrl',
+ link: {mockLink: 'mockLinkValue'},
+ } as any
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (lodashModules.cloneDeep as jest.Mock).mockReturnValue(mockUpdate);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(true);
- (cleanUsernameModules.cleanDisplayName as jest.Mock).mockReturnValue(mockUpdate.name);
- (cleanUsernameModules.cleanUsername as jest.Mock).mockReturnValue(false);
-
- expect(updateMe(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Invalid username');
- });
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(true)
- it('should throw if the username is reserved', async () => {
- const mockProps = {} as any;
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockUpdate = {
- name: "mockName",
- username: "mockUsername",
- avatarUrl: "mockAvatarUrl",
- link: {"mockLink" : "mockLinkValue"},
- };
+ expect(updateMe(mockProps, mockAuth, mockReq)).rejects.toThrow('Invalid username')
+ })
- const arrySpy = jest.spyOn(Array.prototype, 'includes');
+ it('should throw if the username is reserved', async () => {
+ const mockProps = {
+ name: 'mockName',
+ username: 'mockUsername',
+ avatarUrl: 'mockAvatarUrl',
+ link: {mockLink: 'mockLinkValue'},
+ } as any
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- (lodashModules.cloneDeep as jest.Mock).mockReturnValue(mockUpdate);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(true);
- (cleanUsernameModules.cleanDisplayName as jest.Mock).mockReturnValue(mockUpdate.name);
- (cleanUsernameModules.cleanUsername as jest.Mock).mockReturnValue(mockUpdate.username);
- arrySpy.mockReturnValue(true);
+ const arraySpy = jest.spyOn(Array.prototype, 'includes')
- expect(updateMe(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('This username is reserved');
- });
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(true)
+ arraySpy.mockReturnValue(true)
- it('should throw if the username is taken', async () => {
- const mockProps = {} as any;
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockUpdate = {
- name: "mockName",
- username: "mockUsername",
- avatarUrl: "mockAvatarUrl",
- link: {"mockLink" : "mockLinkValue"},
- };
- const arrySpy = jest.spyOn(Array.prototype, 'includes');
+ expect(updateMe(mockProps, mockAuth, mockReq)).rejects.toThrow('This username is reserved')
+ })
- (lodashModules.cloneDeep as jest.Mock).mockReturnValue(mockUpdate);
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(true);
- (cleanUsernameModules.cleanDisplayName as jest.Mock).mockReturnValue(mockUpdate.name);
- (cleanUsernameModules.cleanUsername as jest.Mock).mockReturnValue(mockUpdate.username);
- arrySpy.mockReturnValue(false);
- (sharedUtils.getUserByUsername as jest.Mock).mockReturnValue(true);
+ it('should throw if the username is taken', async () => {
+ const mockProps = {
+ name: 'mockName',
+ username: 'mockUsername',
+ avatarUrl: 'mockAvatarUrl',
+ link: {mockLink: 'mockLinkValue'},
+ } as any
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const arraySpy = jest.spyOn(Array.prototype, 'includes')
- expect(updateMe(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Username already taken');
- });
- });
-});
\ No newline at end of file
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(true)
+ arraySpy.mockReturnValue(false)
+ ;(sharedUtils.getUserByUsername as jest.Mock).mockReturnValue(true)
+
+ expect(updateMe(mockProps, mockAuth, mockReq)).rejects.toThrow('Username already taken')
+ })
+ })
+})
diff --git a/backend/api/tests/unit/update-notif-setting.unit.test.ts b/backend/api/tests/unit/update-notif-setting.unit.test.ts
index fd7578b0..c6fdbdcd 100644
--- a/backend/api/tests/unit/update-notif-setting.unit.test.ts
+++ b/backend/api/tests/unit/update-notif-setting.unit.test.ts
@@ -1,72 +1,69 @@
-jest.mock('shared/supabase/init');
-jest.mock('shared/supabase/users');
-jest.mock('shared/websockets/helpers');
+jest.mock('shared/supabase/init')
+jest.mock('shared/supabase/users')
+jest.mock('shared/websockets/helpers')
-import {sqlMatch} from "common/test-utils";
-import {AuthedUser} from "api/helpers/endpoint";
-import {updateNotifSettings} from "api/update-notif-setting";
-import * as supabaseInit from "shared/supabase/init";
-import * as supabaseUsers from "shared/supabase/users";
-import * as websocketHelpers from "shared/websockets/helpers";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {updateNotifSettings} from 'api/update-notif-setting'
+import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseUsers from 'shared/supabase/users'
+import * as websocketHelpers from 'shared/websockets/helpers'
describe('updateNotifSettings', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- none: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should update notification settings', async () => {
+ const mockProps = {
+ type: 'new_match' as const,
+ medium: 'email' as const,
+ enabled: false,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- describe('when given valid input', () => {
- it('should update notification settings', async () => {
- const mockProps = {
- type: "new_match" as const,
- medium: "email" as const,
- enabled: false
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(mockPg.none as jest.Mock).mockResolvedValue(null)
+ ;(websocketHelpers.broadcastUpdatedPrivateUser as jest.Mock).mockReturnValue(null)
- (mockPg.none as jest.Mock).mockResolvedValue(null);
- (websocketHelpers.broadcastUpdatedPrivateUser as jest.Mock).mockReturnValue(null);
+ await updateNotifSettings(mockProps, mockAuth, mockReq)
- await updateNotifSettings(mockProps, mockAuth, mockReq);
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(sqlMatch('update private_users'), [
+ mockProps.type,
+ mockProps.medium,
+ mockAuth.uid,
+ ])
+ expect(websocketHelpers.broadcastUpdatedPrivateUser).toBeCalledTimes(1)
+ expect(websocketHelpers.broadcastUpdatedPrivateUser).toBeCalledWith(mockAuth.uid)
+ })
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('update private_users'),
- [mockProps.type, mockProps.medium, mockAuth.uid]
- );
- expect(websocketHelpers.broadcastUpdatedPrivateUser).toBeCalledTimes(1);
- expect(websocketHelpers.broadcastUpdatedPrivateUser).toBeCalledWith(mockAuth.uid);
- });
+ it('should turn off notifications', async () => {
+ const mockProps = {
+ type: 'opt_out_all' as const,
+ medium: 'mobile' as const,
+ enabled: true,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- it('should turn off notifications', async () => {
- const mockProps = {
- type: "opt_out_all" as const,
- medium: "mobile" as const,
- enabled: true
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(supabaseUsers.updatePrivateUser as jest.Mock).mockResolvedValue(null)
- (supabaseUsers.updatePrivateUser as jest.Mock).mockResolvedValue(null);
+ await updateNotifSettings(mockProps, mockAuth, mockReq)
- await updateNotifSettings(mockProps, mockAuth, mockReq);
-
- expect(supabaseUsers.updatePrivateUser).toBeCalledTimes(1);
- expect(supabaseUsers.updatePrivateUser).toBeCalledWith(
- expect.any(Object),
- mockAuth.uid,
- {interestedInPushNotifications: !mockProps.enabled}
- );
- });
- });
-});
\ No newline at end of file
+ expect(supabaseUsers.updatePrivateUser).toBeCalledTimes(1)
+ expect(supabaseUsers.updatePrivateUser).toBeCalledWith(expect.any(Object), mockAuth.uid, {
+ interestedInPushNotifications: !mockProps.enabled,
+ })
+ })
+ })
+})
diff --git a/backend/api/tests/unit/update-options.unit.test.ts b/backend/api/tests/unit/update-options.unit.test.ts
index 97626af0..c5cf2239 100644
--- a/backend/api/tests/unit/update-options.unit.test.ts
+++ b/backend/api/tests/unit/update-options.unit.test.ts
@@ -1,177 +1,164 @@
-import {sqlMatch} from "common/test-utils";
-import {AuthedUser} from "api/helpers/endpoint";
-import {updateOptions} from "api/update-options";
-import {tryCatch} from "common/util/try-catch";
-import * as supabaseInit from "shared/supabase/init";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {updateOptions} from 'api/update-options'
+import {sqlMatch} from 'common/test-utils'
+import {tryCatch} from 'common/util/try-catch'
+import * as supabaseInit from 'shared/supabase/init'
-jest.mock('common/util/try-catch');
-jest.mock('shared/supabase/init');
+jest.mock('common/util/try-catch')
+jest.mock('shared/supabase/init')
describe('updateOptions', () => {
- let mockPg = {} as any;
- let mockTx = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockTx = {
- one: jest.fn(),
- none: jest.fn(),
- manyOrNone: jest.fn(),
- };
- mockPg = {
- oneOrNone: jest.fn(),
- manyOrNone: jest.fn(),
- tx: jest.fn(async (cb) => await cb(mockTx))
- };
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ let mockPg = {} as any
+ let mockTx = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockTx = {
+ one: jest.fn(),
+ none: jest.fn(),
+ manyOrNone: jest.fn(),
+ }
+ mockPg = {
+ oneOrNone: jest.fn(),
+ manyOrNone: jest.fn(),
+ tx: jest.fn(async (cb) => await cb(mockTx)),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- describe('when given valid input', () => {
- it('should update user', async () => {
- const mockProps = {
- table: 'causes' as const,
- values: ["mockNamesOne", "mockNamesTwo"]
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockProfileIdResult = {id: 123};
- const mockRow1 = {
- id: 1234,
- };
- const mockRow2 = {
- id: 12345,
- };
+ describe('when given valid input', () => {
+ it('should update user', async () => {
+ const mockProps = {
+ table: 'causes' as const,
+ values: ['mockNamesOne', 'mockNamesTwo'],
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockProfileIdResult = {id: 123}
+ const mockRow1 = {
+ id: 1234,
+ }
+ const mockRow2 = {
+ id: 12345,
+ }
- jest.spyOn(Array.prototype, 'includes').mockReturnValue(true);
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockProfileIdResult);
- (tryCatch as jest.Mock).mockImplementation(async (fn: any) => {
- try {
- const data = await fn;
- return {data, error: null};
- } catch (error) {
- return {data:null, error};
- }
- });
- (mockTx.one as jest.Mock)
- .mockResolvedValueOnce(mockRow1)
- .mockResolvedValueOnce(mockRow2);
- (mockTx.manyOrNone as jest.Mock).mockResolvedValue([]);
-
- const result: any = await updateOptions(mockProps, mockAuth, mockReq);
+ jest.spyOn(Array.prototype, 'includes').mockReturnValue(true)
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockProfileIdResult)
+ ;(tryCatch as jest.Mock).mockImplementation(async (fn: any) => {
+ try {
+ const data = await fn
+ return {data, error: null}
+ } catch (error) {
+ return {data: null, error}
+ }
+ })
+ ;(mockTx.one as jest.Mock).mockResolvedValueOnce(mockRow1).mockResolvedValueOnce(mockRow2)
+ ;(mockTx.manyOrNone as jest.Mock).mockResolvedValue([])
- expect(result.updatedIds).toStrictEqual([mockRow1.id, mockRow2.id]);
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('SELECT id FROM profiles WHERE user_id = $1'),
- [mockAuth.uid]
- );
- expect(tryCatch).toBeCalledTimes(1);
- expect(mockTx.one).toBeCalledTimes(2);
- expect(mockTx.one).toHaveBeenNthCalledWith(
- 1,
- sqlMatch(`INSERT INTO ${mockProps.table} (name, creator_id)`),
- [mockProps.values[0], mockAuth.uid]
- );
- expect(mockTx.one).toHaveBeenNthCalledWith(
- 2,
- sqlMatch(`INSERT INTO ${mockProps.table} (name, creator_id)`),
- [mockProps.values[1], mockAuth.uid]
- );
- expect(mockTx.none).toBeCalledTimes(2);
- expect(mockTx.none).toHaveBeenNthCalledWith(
- 1,
- sqlMatch(`DELETE
+ const result: any = await updateOptions(mockProps, mockAuth, mockReq)
+
+ expect(result.updatedIds).toStrictEqual([mockRow1.id, mockRow2.id])
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(
+ sqlMatch('SELECT id FROM profiles WHERE user_id = $1'),
+ [mockAuth.uid],
+ )
+ expect(tryCatch).toBeCalledTimes(1)
+ expect(mockTx.one).toBeCalledTimes(2)
+ expect(mockTx.one).toHaveBeenNthCalledWith(
+ 1,
+ sqlMatch(`INSERT INTO ${mockProps.table} (name, creator_id)`),
+ [mockProps.values[0], mockAuth.uid],
+ )
+ expect(mockTx.one).toHaveBeenNthCalledWith(
+ 2,
+ sqlMatch(`INSERT INTO ${mockProps.table} (name, creator_id)`),
+ [mockProps.values[1], mockAuth.uid],
+ )
+ expect(mockTx.none).toBeCalledTimes(2)
+ expect(mockTx.none).toHaveBeenNthCalledWith(
+ 1,
+ sqlMatch(`DELETE
FROM profile_${mockProps.table}
WHERE profile_id = $1`),
- [mockProfileIdResult.id]
- );
- expect(mockTx.none).toHaveBeenNthCalledWith(
- 2,
- sqlMatch(`INSERT INTO profile_${mockProps.table} (profile_id, option_id)
+ [mockProfileIdResult.id],
+ )
+ expect(mockTx.none).toHaveBeenNthCalledWith(
+ 2,
+ sqlMatch(`INSERT INTO profile_${mockProps.table} (profile_id, option_id)
VALUES`),
- [mockProfileIdResult.id, mockRow1.id, mockRow2.id]
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if the table param is invalid', async () => {
- const mockProps = {
- table: 'causes' as const,
- values: ["mockNamesOne", "mockNamesTwo"]
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ [mockProfileIdResult.id, mockRow1.id, mockRow2.id],
+ )
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if the table param is invalid', async () => {
+ const mockProps = {
+ table: 'causes' as const,
+ values: ['mockNamesOne', 'mockNamesTwo'],
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- jest.spyOn(Array.prototype, 'includes').mockReturnValue(false);
-
- expect(updateOptions(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Invalid table');
- });
+ jest.spyOn(Array.prototype, 'includes').mockReturnValue(false)
- it('should throw if the names param is not provided', async () => {
- const mockProps = {
- table: 'causes' as const,
- values: undefined
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ expect(updateOptions(mockProps, mockAuth, mockReq)).rejects.toThrow('Invalid table')
+ })
- jest.spyOn(Array.prototype, 'includes').mockReturnValue(true);
-
- expect(updateOptions(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('No ids provided');
- });
+ it('should throw if the names param is not provided', async () => {
+ const mockProps = {
+ table: 'causes' as const,
+ values: undefined,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- it('should throw if unable to find profile', async () => {
- const mockProps = {
- table: 'causes' as const,
- values: ["mockNamesOne", "mockNamesTwo"]
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ jest.spyOn(Array.prototype, 'includes').mockReturnValue(true)
- jest.spyOn(Array.prototype, 'includes').mockReturnValue(true);
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(false);
-
- expect(updateOptions(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Profile not found');
- });
+ expect(updateOptions(mockProps, mockAuth, mockReq)).rejects.toThrow('No ids provided')
+ })
- it('should update user', async () => {
- const mockProps = {
- table: 'causes' as const,
- values: ["mockNamesOne", "mockNamesTwo"]
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockProfileIdResult = {id: 123};
- const mockRow1 = {
- id: 1234,
- };
- const mockRow2 = {
- id: 12345,
- };
+ it('should throw if unable to find profile', async () => {
+ const mockProps = {
+ table: 'causes' as const,
+ values: ['mockNamesOne', 'mockNamesTwo'],
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- jest.spyOn(Array.prototype, 'includes').mockReturnValue(true);
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockProfileIdResult);
- (tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error});
- (mockPg.tx as jest.Mock).mockResolvedValue(null);
- (mockTx.one as jest.Mock)
- .mockResolvedValueOnce(mockRow1)
- .mockResolvedValueOnce(mockRow2);
- (mockTx.none as jest.Mock)
- .mockResolvedValueOnce(null)
- .mockResolvedValueOnce(null);
-
- expect(updateOptions(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Error updating profile options');
- });
- });
-});
\ No newline at end of file
+ jest.spyOn(Array.prototype, 'includes').mockReturnValue(true)
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(false)
+
+ expect(updateOptions(mockProps, mockAuth, mockReq)).rejects.toThrow('Profile not found')
+ })
+
+ it('should update user', async () => {
+ const mockProps = {
+ table: 'causes' as const,
+ values: ['mockNamesOne', 'mockNamesTwo'],
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockProfileIdResult = {id: 123}
+ const mockRow1 = {
+ id: 1234,
+ }
+ const mockRow2 = {
+ id: 12345,
+ }
+
+ jest.spyOn(Array.prototype, 'includes').mockReturnValue(true)
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(mockProfileIdResult)
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: null, error: Error})
+ ;(mockPg.tx as jest.Mock).mockResolvedValue(null)
+ ;(mockTx.one as jest.Mock).mockResolvedValueOnce(mockRow1).mockResolvedValueOnce(mockRow2)
+ ;(mockTx.none as jest.Mock).mockResolvedValueOnce(null).mockResolvedValueOnce(null)
+
+ expect(updateOptions(mockProps, mockAuth, mockReq)).rejects.toThrow(
+ 'Error updating profile options',
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/update-private-user-message-channel.unit.test.ts b/backend/api/tests/unit/update-private-user-message-channel.unit.test.ts
index 1b74216e..810ea8df 100644
--- a/backend/api/tests/unit/update-private-user-message-channel.unit.test.ts
+++ b/backend/api/tests/unit/update-private-user-message-channel.unit.test.ts
@@ -1,92 +1,90 @@
-import {sqlMatch} from "common/test-utils";
-import {updatePrivateUserMessageChannel} from "api/update-private-user-message-channel";
-import * as supabaseInit from "shared/supabase/init";
-import * as sharedUtils from "shared/utils";
-import * as supabaseUtils from "common/supabase/utils";
-import {AuthedUser} from "api/helpers/endpoint";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {updatePrivateUserMessageChannel} from 'api/update-private-user-message-channel'
+import * as supabaseUtils from 'common/supabase/utils'
+import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
+import * as sharedUtils from 'shared/utils'
-jest.mock('shared/supabase/init');
-jest.mock('shared/utils');
-jest.mock('common/supabase/utils');
+jest.mock('shared/supabase/init')
+jest.mock('shared/utils')
+jest.mock('common/supabase/utils')
describe('updatePrivateUserMessageChannel', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- none: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ none: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should return success after updating the users private message channel', async () => {
+ const mockBody = {
+ channelId: 123,
+ notifyAfterTime: 10,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- describe('when given valid input', () => {
- it('should return success after updating the users private message channel', async () => {
- const mockBody = {
- channelId: 123,
- notifyAfterTime: 10
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
-
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(true);
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(true);
- (supabaseUtils.millisToTs as jest.Mock).mockReturnValue('mockMillisToTs');
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(true)
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(true)
+ ;(supabaseUtils.millisToTs as jest.Mock).mockReturnValue('mockMillisToTs')
- const results = await updatePrivateUserMessageChannel(mockBody, mockAuth, mockReq);
+ const results = await updatePrivateUserMessageChannel(mockBody, mockAuth, mockReq)
- expect(results.status).toBe('success');
- expect(results.channelId).toBe(mockBody.channelId);
- expect(sharedUtils.getUser).toBeCalledTimes(1);
- expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid);
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select status from private_user_message_channel_members'),
- [mockBody.channelId, mockAuth.uid]
- );
- expect(mockPg.none).toBeCalledTimes(1);
- expect(mockPg.none).toBeCalledWith(
- sqlMatch('update private_user_message_channel_members'),
- [mockBody.channelId, mockAuth.uid, 'mockMillisToTs']
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if the user account does not exist', async () => {
- const mockBody = {
- channelId: 123,
- notifyAfterTime: 10
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
-
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
+ expect(results.status).toBe('success')
+ expect(results.channelId).toBe(mockBody.channelId)
+ expect(sharedUtils.getUser).toBeCalledTimes(1)
+ expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid)
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(
+ sqlMatch('select status from private_user_message_channel_members'),
+ [mockBody.channelId, mockAuth.uid],
+ )
+ expect(mockPg.none).toBeCalledTimes(1)
+ expect(mockPg.none).toBeCalledWith(sqlMatch('update private_user_message_channel_members'), [
+ mockBody.channelId,
+ mockAuth.uid,
+ 'mockMillisToTs',
+ ])
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if the user account does not exist', async () => {
+ const mockBody = {
+ channelId: 123,
+ notifyAfterTime: 10,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- expect(updatePrivateUserMessageChannel(mockBody, mockAuth, mockReq))
- .rejects
- .toThrow('Your account was not found');
- });
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
- it('should throw if the user is not authorized in the channel', async () => {
- const mockBody = {
- channelId: 123,
- notifyAfterTime: 10
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
-
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(true);
- (mockPg.oneOrNone as jest.Mock).mockResolvedValue(false);
+ expect(updatePrivateUserMessageChannel(mockBody, mockAuth, mockReq)).rejects.toThrow(
+ 'Your account was not found',
+ )
+ })
- expect(updatePrivateUserMessageChannel(mockBody, mockAuth, mockReq))
- .rejects
- .toThrow('You are not authorized to this channel');
+ it('should throw if the user is not authorized in the channel', async () => {
+ const mockBody = {
+ channelId: 123,
+ notifyAfterTime: 10,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- });
- });
-});
\ No newline at end of file
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(true)
+ ;(mockPg.oneOrNone as jest.Mock).mockResolvedValue(false)
+
+ expect(updatePrivateUserMessageChannel(mockBody, mockAuth, mockReq)).rejects.toThrow(
+ 'You are not authorized to this channel',
+ )
+ })
+ })
+})
diff --git a/backend/api/tests/unit/update-profile.unit.test.ts b/backend/api/tests/unit/update-profile.unit.test.ts
index 3880c3fa..8de010ed 100644
--- a/backend/api/tests/unit/update-profile.unit.test.ts
+++ b/backend/api/tests/unit/update-profile.unit.test.ts
@@ -1,101 +1,93 @@
-jest.mock("shared/supabase/init");
-jest.mock("shared/supabase/utils");
-jest.mock("common/util/try-catch");
-jest.mock("shared/profiles/parse-photos");
-jest.mock("shared/supabase/users");
+jest.mock('shared/supabase/init')
+jest.mock('shared/supabase/utils')
+jest.mock('common/util/try-catch')
+jest.mock('shared/profiles/parse-photos')
+jest.mock('shared/supabase/users')
-import {updateProfile} from "api/update-profile";
-import {AuthedUser} from "api/helpers/endpoint";
-import * as supabaseInit from "shared/supabase/init";
-import * as supabaseUtils from "shared/supabase/utils";
-import * as supabaseUsers from "shared/supabase/users";
-import {tryCatch} from "common/util/try-catch";
-import {removePinnedUrlFromPhotoUrls} from "shared/profiles/parse-photos";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {updateProfile} from 'api/update-profile'
import {sqlMatch} from 'common/test-utils'
+import {tryCatch} from 'common/util/try-catch'
+import {removePinnedUrlFromPhotoUrls} from 'shared/profiles/parse-photos'
+import * as supabaseInit from 'shared/supabase/init'
+import * as supabaseUsers from 'shared/supabase/users'
+import * as supabaseUtils from 'shared/supabase/utils'
describe('updateProfiles', () => {
- let mockPg: any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- oneOrNone: jest.fn(),
- };
+ let mockPg: any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ oneOrNone: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
+ describe('when given valid input', () => {
+ it('should update an existing profile when provided the user id', async () => {
+ const mockProps = {
+ pinned_url: 'mockAvatarUrl',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockData = 'success'
- describe('when given valid input', () => {
- it('should update an existing profile when provided the user id', async () => {
- const mockProps = {
- pinned_url: "mockAvatarUrl"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockData = "success";
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce({data: true})
+ .mockResolvedValueOnce({data: mockData, error: null})
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce({data: true})
- .mockResolvedValueOnce({data: mockData, error: null});
+ const result = await updateProfile(mockProps, mockAuth, mockReq)
- const result = await updateProfile(mockProps, mockAuth, mockReq);
+ expect(result).toBe(mockData)
+ expect(mockPg.oneOrNone).toBeCalledTimes(1)
+ expect(mockPg.oneOrNone).toBeCalledWith(
+ sqlMatch('select * from profiles where user_id = $1'),
+ [mockAuth.uid],
+ )
+ expect(removePinnedUrlFromPhotoUrls).toBeCalledTimes(1)
+ expect(removePinnedUrlFromPhotoUrls).toBeCalledWith(mockProps)
+ expect(supabaseUsers.updateUser).toBeCalledTimes(1)
+ expect(supabaseUsers.updateUser).toBeCalledWith(expect.any(Object), mockAuth.uid, {
+ avatarUrl: mockProps.pinned_url,
+ })
+ expect(supabaseUtils.update).toBeCalledTimes(1)
+ expect(supabaseUtils.update).toBeCalledWith(
+ expect.any(Object),
+ 'profiles',
+ 'user_id',
+ expect.any(Object),
+ )
+ })
+ })
- expect(result).toBe(mockData);
- expect(mockPg.oneOrNone).toBeCalledTimes(1);
- expect(mockPg.oneOrNone).toBeCalledWith(
- sqlMatch('select * from profiles where user_id = $1'),
- [mockAuth.uid]
- );
- expect(removePinnedUrlFromPhotoUrls).toBeCalledTimes(1);
- expect(removePinnedUrlFromPhotoUrls).toBeCalledWith(mockProps);
- expect(supabaseUsers.updateUser).toBeCalledTimes(1);
- expect(supabaseUsers.updateUser).toBeCalledWith(
- expect.any(Object),
- mockAuth.uid,
- {avatarUrl: mockProps.pinned_url}
- );
- expect(supabaseUtils.update).toBeCalledTimes(1);
- expect(supabaseUtils.update).toBeCalledWith(
- expect.any(Object),
- 'profiles',
- 'user_id',
- expect.any(Object)
- );
- });
- });
+ describe('when an error occurs', () => {
+ it('should throw if the profile does not exist', async () => {
+ const mockProps = {
+ avatar_url: 'mockAvatarUrl',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- describe('when an error occurs', () => {
- it('should throw if the profile does not exist', async () => {
- const mockProps = {
- avatar_url: "mockAvatarUrl"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(tryCatch as jest.Mock).mockResolvedValue({data: false})
- (tryCatch as jest.Mock).mockResolvedValue({data: false});
+ expect(updateProfile(mockProps, mockAuth, mockReq)).rejects.toThrow('Profile not found')
+ })
- expect(updateProfile(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Profile not found');
- });
+ it('should throw if unable to update the profile', async () => {
+ const mockProps = {
+ avatar_url: 'mockAvatarUrl',
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- it('should throw if unable to update the profile', async () => {
- const mockProps = {
- avatar_url: "mockAvatarUrl"
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(tryCatch as jest.Mock)
+ .mockResolvedValueOnce({data: true})
+ .mockResolvedValueOnce({data: null, error: Error})
- (tryCatch as jest.Mock)
- .mockResolvedValueOnce({data: true})
- .mockResolvedValueOnce({data: null, error: Error});
-
- expect(updateProfile(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Error updating profile');
- });
- });
-});
\ No newline at end of file
+ expect(updateProfile(mockProps, mockAuth, mockReq)).rejects.toThrow('Error updating profile')
+ })
+ })
+})
diff --git a/backend/api/tests/unit/vote-unit.test.ts b/backend/api/tests/unit/vote-unit.test.ts
index 94e960f8..d2f56a2d 100644
--- a/backend/api/tests/unit/vote-unit.test.ts
+++ b/backend/api/tests/unit/vote-unit.test.ts
@@ -1,102 +1,94 @@
-jest.mock('shared/supabase/init');
-jest.mock('shared/utils');
+jest.mock('shared/supabase/init')
+jest.mock('shared/utils')
-import {AuthedUser} from "api/helpers/endpoint";
-import {vote} from "api/vote";
-import * as supabaseInit from "shared/supabase/init";
-import * as sharedUtils from "shared/utils";
+import {AuthedUser} from 'api/helpers/endpoint'
+import {vote} from 'api/vote'
import {sqlMatch} from 'common/test-utils'
+import * as supabaseInit from 'shared/supabase/init'
+import * as sharedUtils from 'shared/utils'
describe('vote', () => {
- let mockPg = {} as any;
- beforeEach(() => {
- jest.resetAllMocks();
- mockPg = {
- one: jest.fn()
- };
+ let mockPg = {} as any
+ beforeEach(() => {
+ jest.resetAllMocks()
+ mockPg = {
+ one: jest.fn(),
+ }
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
+ afterEach(() => {
+ jest.restoreAllMocks()
+ })
+ describe('when given valid input', () => {
+ it('should vote successfully', async () => {
+ const mockProps = {
+ voteId: 1,
+ choice: 'for' as const,
+ priority: 10,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockUser = {id: 'mockUserId'}
+ const mockResult = 'success'
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
- afterEach(() => {
- jest.restoreAllMocks();
- });
- describe('when given valid input', () => {
- it('should vote successfully', async () => {
- const mockProps = {
- voteId: 1,
- choice: 'for' as const,
- priority: 10
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockUser = {id: "mockUserId"};
- const mockResult = "success";
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
+ ;(mockPg.one as jest.Mock).mockResolvedValue(mockResult)
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
- (mockPg.one as jest.Mock).mockResolvedValue(mockResult);
+ const result = await vote(mockProps, mockAuth, mockReq)
- const result = await vote(mockProps, mockAuth, mockReq);
+ expect(result.data).toBe(mockResult)
+ expect(sharedUtils.getUser).toBeCalledTimes(1)
+ expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid)
+ expect(mockPg.one).toBeCalledTimes(1)
+ expect(mockPg.one).toBeCalledWith(
+ sqlMatch('insert into vote_results (user_id, vote_id, choice, priority)'),
+ [mockUser.id, mockProps.voteId, 1, mockProps.priority],
+ )
+ })
+ })
+ describe('when an error occurs', () => {
+ it('should throw if unable to find the account', async () => {
+ const mockProps = {
+ voteId: 1,
+ choice: 'for' as const,
+ priority: 10,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
- expect(result.data).toBe(mockResult);
- expect(sharedUtils.getUser).toBeCalledTimes(1);
- expect(sharedUtils.getUser).toBeCalledWith(mockAuth.uid);
- expect(mockPg.one).toBeCalledTimes(1);
- expect(mockPg.one).toBeCalledWith(
- sqlMatch('insert into vote_results (user_id, vote_id, choice, priority)'),
- [mockUser.id, mockProps.voteId, 1, mockProps.priority]
- );
- });
- });
- describe('when an error occurs', () => {
- it('should throw if unable to find the account', async () => {
- const mockProps = {
- voteId: 1,
- choice: 'for' as const,
- priority: 10
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(false)
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(false);
+ expect(vote(mockProps, mockAuth, mockReq)).rejects.toThrow('Your account was not found')
+ })
- expect(vote(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Your account was not found');
- });
+ it('should throw if the choice is invalid', async () => {
+ const mockProps = {
+ voteId: 1,
+ priority: 10,
+ } as any
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockUser = {id: 'mockUserId'}
- it('should throw if the choice is invalid', async () => {
- const mockProps = {
- voteId: 1,
- priority: 10
- } as any;
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockUser = {id: "mockUserId"};
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
+ expect(vote(mockProps, mockAuth, mockReq)).rejects.toThrow('Invalid choice')
+ })
- expect(vote(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Invalid choice');
- });
+ it('should throw if unable to record vote', async () => {
+ const mockProps = {
+ voteId: 1,
+ choice: 'for' as const,
+ priority: 10,
+ }
+ const mockAuth = {uid: '321'} as AuthedUser
+ const mockReq = {} as any
+ const mockUser = {id: 'mockUserId'}
- it('should throw if unable to record vote', async () => {
- const mockProps = {
- voteId: 1,
- choice: 'for' as const,
- priority: 10
- };
- const mockAuth = { uid: '321' } as AuthedUser;
- const mockReq = {} as any;
- const mockUser = {id: "mockUserId"};
+ ;(sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser)
+ ;(mockPg.one as jest.Mock).mockRejectedValue(new Error('Result error'))
- (sharedUtils.getUser as jest.Mock).mockResolvedValue(mockUser);
- (mockPg.one as jest.Mock).mockRejectedValue(new Error('Result error'));
-
- expect(vote(mockProps, mockAuth, mockReq))
- .rejects
- .toThrow('Error recording vote');
- });
- });
-});
\ No newline at end of file
+ expect(vote(mockProps, mockAuth, mockReq)).rejects.toThrow('Error recording vote')
+ })
+ })
+})
diff --git a/backend/api/tsconfig.json b/backend/api/tsconfig.json
index 70ee3aa5..348df297 100644
--- a/backend/api/tsconfig.json
+++ b/backend/api/tsconfig.json
@@ -1,11 +1,8 @@
{
"compilerOptions": {
- "rootDir": "src",
- "composite": true,
"module": "commonjs",
"noImplicitReturns": true,
"outDir": "./lib",
- "tsBuildInfoFile": "lib/tsconfig.tsbuildinfo",
"sourceMap": true,
"strict": true,
"resolveJsonModule": true,
@@ -15,43 +12,16 @@
"skipLibCheck": true,
"jsx": "react-jsx",
"paths": {
- "common/*": [
- "../../common/src/*",
- "../../../common/lib/*"
- ],
- "shared/*": [
- "../shared/src/*",
- "../../shared/lib/*"
- ],
- "email/*": [
- "../email/emails/*",
- "../../email/lib/*"
- ],
- "api/*": [
- "./src/*"
- ]
+ "common/*": ["../../common/src/*", "../../../common/lib/*"],
+ "shared/*": ["../shared/src/*", "../../shared/lib/*"],
+ "email/*": ["../email/emails/*", "../../email/lib/*"],
+ "api/*": ["./src/*"]
}
},
"ts-node": {
- "require": [
- "tsconfig-paths/register"
- ]
+ "require": ["tsconfig-paths/register"]
},
- "references": [
- {
- "path": "../../common"
- },
- {
- "path": "../shared"
- },
- {
- "path": "../email"
- }
- ],
"compileOnSave": true,
- "include": [
- "src/**/*.ts",
- "package.json",
- "backend/api/package.json"
- ]
+ "include": ["src/**/*.ts", "package.json", "backend/api/package.json"],
+ "exclude": ["**/*.test.ts", "**/*.spec.ts"]
}
diff --git a/backend/api/tsconfig.test.json b/backend/api/tsconfig.test.json
index 6c128f30..dbe5af44 100644
--- a/backend/api/tsconfig.test.json
+++ b/backend/api/tsconfig.test.json
@@ -13,10 +13,6 @@
"email/*": ["../email/emails/*"]
}
},
- "include": [
- "tests/**/*.ts",
- "src/**/*.ts",
- "../shared/src/**/*.ts",
- "../../common/src/**/*.ts"
- ]
+ "include": ["tests/**/*.ts", "src/**/*.ts", "../shared/src/**/*.ts", "../../common/src/**/*.ts"],
+ "exclude": []
}
diff --git a/backend/email/.prettierignore b/backend/email/.prettierignore
new file mode 100644
index 00000000..d4cf806b
--- /dev/null
+++ b/backend/email/.prettierignore
@@ -0,0 +1,34 @@
+# Dependencies
+node_modules
+.yarn
+
+# Build outputs
+dist
+build
+.next
+out
+lib
+
+# Generated files
+coverage
+*.min.js
+*.min.css
+
+# Database / migrations
+**/*.sql
+
+# Config / lock files
+yarn.lock
+package-lock.json
+pnpm-lock.yaml
+
+# Android / iOS
+android
+ios
+capacitor.config.ts
+
+# Playwright
+tests/reports
+playwright-report
+
+coverage
\ No newline at end of file
diff --git a/backend/email/README.md b/backend/email/README.md
index 062fd2cf..992a50de 100644
--- a/backend/email/README.md
+++ b/backend/email/README.md
@@ -18,12 +18,12 @@ yarn dev
Open [localhost:3001](http://localhost:3001) with your browser to see the result.
-
### Notes
Right now, I can't make the email server run without breaking the backend API and web, as they require different versions of react.
To run the email server, temporarily install the deps in this folder. They require react 19.
+
```bash
yarn add -D @react-email/preview-server react-email
```
@@ -34,4 +34,4 @@ When you are done, reinstall react 18.2 by running `yarn clean-install` at the r
```bash
-```
\ No newline at end of file
+```
diff --git a/backend/email/emails/functions/helpers.tsx b/backend/email/emails/functions/helpers.tsx
index 4fa16e5a..557d1b59 100644
--- a/backend/email/emails/functions/helpers.tsx
+++ b/backend/email/emails/functions/helpers.tsx
@@ -1,17 +1,20 @@
-import React from 'react';
+import React from 'react'
import {PrivateUser, User} from 'common/user'
-import {getNotificationDestinationsForUser, UNSUBSCRIBE_URL} from 'common/user-notification-preferences'
+import {
+ getNotificationDestinationsForUser,
+ UNSUBSCRIBE_URL,
+} from 'common/user-notification-preferences'
import {sendEmail} from './send-email'
import {NewMessageEmail} from '../new-message'
import {NewEndorsementEmail} from '../new-endorsement'
import {Test} from '../test'
import {getProfile} from 'shared/profiles/supabase'
-import {render} from "@react-email/render"
-import {MatchesType} from "common/profiles/bookmarked_searches";
-import NewSearchAlertsEmail from "email/new-search_alerts";
-import WelcomeEmail from "email/welcome";
-import * as admin from "firebase-admin";
-import {getOptionsIdsToLabels} from "shared/supabase/options";
+import {render} from '@react-email/render'
+import {MatchesType} from 'common/profiles/bookmarked_searches'
+import NewSearchAlertsEmail from 'email/new-search_alerts'
+import WelcomeEmail from 'email/welcome'
+import * as admin from 'firebase-admin'
+import {getOptionsIdsToLabels} from 'shared/supabase/options'
export const fromEmail = 'Compass '
@@ -47,11 +50,11 @@ export const sendNewMessageEmail = async (
privateUser: PrivateUser,
fromUser: User,
toUser: User,
- channelId: number
+ channelId: number,
) => {
const {sendToEmail, unsubscribeUrl} = getNotificationDestinationsForUser(
privateUser,
- 'new_message'
+ 'new_message',
)
if (!privateUser.email || !sendToEmail) return
@@ -74,17 +77,14 @@ export const sendNewMessageEmail = async (
channelId={channelId}
unsubscribeUrl={unsubscribeUrl}
email={privateUser.email}
- />
+ />,
),
})
}
-export const sendWelcomeEmail = async (
- toUser: User,
- privateUser: PrivateUser,
-) => {
+export const sendWelcomeEmail = async (toUser: User, privateUser: PrivateUser) => {
if (!privateUser.email) return
- const verificationLink = await admin.auth().generateEmailVerificationLink(privateUser.email);
+ const verificationLink = await admin.auth().generateEmailVerificationLink(privateUser.email)
return await sendEmail({
from: fromEmail,
subject: `Welcome to Compass!`,
@@ -95,7 +95,7 @@ export const sendWelcomeEmail = async (
unsubscribeUrl={UNSUBSCRIBE_URL}
email={privateUser.email}
verificationLink={verificationLink}
- />
+ />,
),
})
}
@@ -107,9 +107,9 @@ export const sendSearchAlertsEmail = async (
) => {
const {sendToEmail, unsubscribeUrl} = getNotificationDestinationsForUser(
privateUser,
- 'new_search_alerts'
+ 'new_search_alerts',
)
- const email = privateUser.email;
+ const email = privateUser.email
if (!email || !sendToEmail) return
// Determine locale (fallback to 'en') and load option labels before rendering
@@ -128,7 +128,7 @@ export const sendSearchAlertsEmail = async (
unsubscribeUrl={unsubscribeUrl}
email={email}
optionIdsToLabels={optionIdsToLabels}
- />
+ />,
),
})
}
@@ -137,11 +137,11 @@ export const sendNewEndorsementEmail = async (
privateUser: PrivateUser,
fromUser: User,
onUser: User,
- text: string
+ text: string,
) => {
const {sendToEmail, unsubscribeUrl} = getNotificationDestinationsForUser(
privateUser,
- 'new_endorsement'
+ 'new_endorsement',
)
if (!privateUser.email || !sendToEmail) return
@@ -156,7 +156,7 @@ export const sendNewEndorsementEmail = async (
endorsementText={text}
unsubscribeUrl={unsubscribeUrl}
email={privateUser.email}
- />
+ />,
),
})
}
@@ -166,6 +166,6 @@ export const sendTestEmail = async (toEmail: string) => {
from: fromEmail,
subject: 'Test email from Compass',
to: toEmail,
- html: await render( ),
+ html: await render( ),
})
}
diff --git a/backend/email/emails/functions/send-email.ts b/backend/email/emails/functions/send-email.ts
index ed32de65..413eec07 100644
--- a/backend/email/emails/functions/send-email.ts
+++ b/backend/email/emails/functions/send-email.ts
@@ -1,36 +1,33 @@
-import {type CreateEmailOptions, CreateEmailRequestOptions, Resend,} from 'resend'
+import {type CreateEmailOptions, CreateEmailRequestOptions, Resend} from 'resend'
import {log} from 'shared/utils'
-import {sleep} from "common/util/time";
-
+import {sleep} from 'common/util/time'
/*
* typically: { subject: string, to: string | string[] } & ({ text: string } | { react: ReactNode })
*/
export const sendEmail = async (
payload: CreateEmailOptions,
- options?: CreateEmailRequestOptions
+ options?: CreateEmailRequestOptions,
) => {
const resend = getResend()
console.debug(resend, payload, options)
const skip = false
if (skip) {
- console.warn("Skipping email send")
+ console.warn('Skipping email send')
return null
}
if (!resend) return null
- const { data, error } = await resend.emails.send(
- { replyTo: 'Compass ', ...payload },
- options
+ const {data, error} = await resend.emails.send(
+ {replyTo: 'Compass ', ...payload},
+ options,
)
console.debug('resend.emails.send', data, error)
if (error) {
- log.error(
- `Failed to send email to ${payload.to} with subject ${payload.subject}`
- )
+ log.error(`Failed to send email to ${payload.to} with subject ${payload.subject}`)
log.error(error)
return null
}
diff --git a/backend/email/emails/functions/send-test-email.ts b/backend/email/emails/functions/send-test-email.ts
index b54baa7a..14983bc5 100755
--- a/backend/email/emails/functions/send-test-email.ts
+++ b/backend/email/emails/functions/send-test-email.ts
@@ -1,4 +1,4 @@
-import { sendTestEmail } from './helpers'
+import {sendTestEmail} from './helpers'
if (require.main === module) {
const email = process.argv[2]
@@ -11,4 +11,4 @@ if (require.main === module) {
sendTestEmail(email)
.then(() => console.debug('Email sent successfully!'))
.catch((error) => console.error('Failed to send email:', error))
-}
\ No newline at end of file
+}
diff --git a/backend/email/emails/new-endorsement.tsx b/backend/email/emails/new-endorsement.tsx
index 1eb7f857..19d210d3 100644
--- a/backend/email/emails/new-endorsement.tsx
+++ b/backend/email/emails/new-endorsement.tsx
@@ -1,9 +1,20 @@
-import React from 'react';
-import {Body, Button, Column, Container, Head, Html, Preview, Row, Section, Text,} from '@react-email/components'
+import React from 'react'
+import {
+ Body,
+ Button,
+ Column,
+ Container,
+ Head,
+ Html,
+ Preview,
+ Row,
+ Section,
+ Text,
+} from '@react-email/components'
import {type User} from 'common/user'
import {DOMAIN} from 'common/envs/constants'
import {jamesUser, mockUser} from './functions/mock'
-import {button, container, content, Footer, main, paragraph} from "email/utils";
+import {button, container, content, Footer, main, paragraph} from 'email/utils'
interface NewEndorsementEmailProps {
fromUser: User
@@ -14,19 +25,19 @@ interface NewEndorsementEmailProps {
}
export const NewEndorsementEmail = ({
- fromUser,
- onUser,
- endorsementText,
- unsubscribeUrl,
- email,
- }: NewEndorsementEmailProps) => {
+ fromUser,
+ onUser,
+ endorsementText,
+ unsubscribeUrl,
+ email,
+}: NewEndorsementEmailProps) => {
const name = onUser.name.split(' ')[0]
const endorsementUrl = `https://${DOMAIN}/${onUser.username}`
return (
-
+
New endorsement from {fromUser.name}
@@ -66,7 +77,7 @@ export const NewEndorsementEmail = ({
-
+
@@ -82,7 +93,6 @@ NewEndorsementEmail.PreviewProps = {
email: 'someone@gmail.com',
} as NewEndorsementEmailProps
-
const endorsementContainer = {
margin: '20px 0',
padding: '15px',
diff --git a/backend/email/emails/new-match.tsx b/backend/email/emails/new-match.tsx
index acf74070..df868306 100644
--- a/backend/email/emails/new-match.tsx
+++ b/backend/email/emails/new-match.tsx
@@ -1,10 +1,10 @@
-import React from 'react';
-import {Body, Button, Container, Head, Html, Preview, Section, Text,} from '@react-email/components'
+import React from 'react'
+import {Body, Button, Container, Head, Html, Preview, Section, Text} from '@react-email/components'
import {DOMAIN} from 'common/envs/constants'
import {type ProfileRow} from 'common/profiles/profile'
import {type User} from 'common/user'
import {jamesProfile, jamesUser, mockUser} from './functions/mock'
-import {Footer} from "email/utils";
+import {Footer} from 'email/utils'
interface NewMatchEmailProps {
onUser: User
@@ -15,23 +15,22 @@ interface NewMatchEmailProps {
}
export const NewMatchEmail = ({
- onUser,
- matchedWithUser,
- // matchedProfile,
- unsubscribeUrl,
- email
- }: NewMatchEmailProps) => {
+ onUser,
+ matchedWithUser,
+ // matchedProfile,
+ unsubscribeUrl,
+ email,
+}: NewMatchEmailProps) => {
const name = onUser.name.split(' ')[0]
// const userImgSrc = getOgImageUrl(matchedWithUser, matchedProfile)
const userUrl = `https://${DOMAIN}/${matchedWithUser.username}`
return (
-
+
You have a new match!
-
{/**/}
{/*
Hi {name},
-
- {matchedWithUser.name} just matched with you!
-
+ {matchedWithUser.name} just matched with you!
{/* */}
@@ -63,7 +60,7 @@ export const NewMatchEmail = ({
-
+
diff --git a/backend/email/emails/new-message.tsx b/backend/email/emails/new-message.tsx
index 7da89312..a168c65e 100644
--- a/backend/email/emails/new-message.tsx
+++ b/backend/email/emails/new-message.tsx
@@ -1,10 +1,10 @@
-import React from 'react';
-import {Body, Button, Container, Head, Html, Preview, Section, Text,} from '@react-email/components'
+import React from 'react'
+import {Body, Button, Container, Head, Html, Preview, Section, Text} from '@react-email/components'
import {type User} from 'common/user'
import {type ProfileRow} from 'common/profiles/profile'
-import {jamesProfile, jamesUser, mockUser,} from './functions/mock'
+import {jamesProfile, jamesUser, mockUser} from './functions/mock'
import {DOMAIN} from 'common/envs/constants'
-import {button, container, content, Footer, imageContainer, main, paragraph} from "email/utils";
+import {button, container, content, Footer, imageContainer, main, paragraph} from 'email/utils'
interface NewMessageEmailProps {
fromUser: User
@@ -16,13 +16,13 @@ interface NewMessageEmailProps {
}
export const NewMessageEmail = ({
- fromUser,
- // fromUserProfile,
- toUser,
- channelId,
- unsubscribeUrl,
- email,
- }: NewMessageEmailProps) => {
+ fromUser,
+ // fromUserProfile,
+ toUser,
+ channelId,
+ unsubscribeUrl,
+ email,
+}: NewMessageEmailProps) => {
const name = toUser.name.split(' ')[0]
const creatorName = fromUser.name
const messagesUrl = `https://${DOMAIN}/messages/${channelId}`
@@ -30,7 +30,7 @@ export const NewMessageEmail = ({
return (
-
+
New message from {creatorName}
@@ -65,7 +65,7 @@ export const NewMessageEmail = ({
-
+
@@ -81,5 +81,4 @@ NewMessageEmail.PreviewProps = {
unsubscribeUrl: 'https://compassmeet.com/unsubscribe',
} as NewMessageEmailProps
-
export default NewMessageEmail
diff --git a/backend/email/emails/new-search_alerts.tsx b/backend/email/emails/new-search_alerts.tsx
index 6a216efa..2ea2d028 100644
--- a/backend/email/emails/new-search_alerts.tsx
+++ b/backend/email/emails/new-search_alerts.tsx
@@ -1,12 +1,12 @@
-import React from 'react';
-import {Body, Container, Head, Html, Link, Preview, Section, Text,} from '@react-email/components'
+import React from 'react'
+import {Body, Container, Head, Html, Link, Preview, Section, Text} from '@react-email/components'
import {type User} from 'common/user'
-import {mockUser,} from './functions/mock'
+import {mockUser} from './functions/mock'
import {DOMAIN} from 'common/envs/constants'
-import {container, content, Footer, main, paragraph} from "email/utils";
-import {MatchesType} from "common/profiles/bookmarked_searches";
-import {formatFilters, locationType} from "common/searches"
-import {FilterFields} from "common/filters";
+import {container, content, Footer, main, paragraph} from 'email/utils'
+import {MatchesType} from 'common/profiles/bookmarked_searches'
+import {formatFilters, locationType} from 'common/searches'
+import {FilterFields} from 'common/filters'
interface NewMessageEmailProps {
toUser: User
@@ -17,17 +17,17 @@ interface NewMessageEmailProps {
}
export const NewSearchAlertsEmail = ({
- toUser,
- unsubscribeUrl,
- matches,
- email,
- optionIdsToLabels = {},
- }: NewMessageEmailProps) => {
+ toUser,
+ unsubscribeUrl,
+ matches,
+ email,
+ optionIdsToLabels = {},
+}: NewMessageEmailProps) => {
const name = toUser.name.split(' ')[0]
return (
-
+
New people share your values — reach out and connect
@@ -35,112 +35,106 @@ export const NewSearchAlertsEmail = ({
Hi {name},
- In the past 24 hours, new people joined Compass whose values and
- interests align with your saved searches. Compass is a gift from the
- community, and it comes alive when people like you take the step to
- connect with one another.
+ In the past 24 hours, new people joined Compass whose values and interests align with
+ your saved searches. Compass is a gift from the community, and it comes alive when
+ people like you take the step to connect with one another.
{(matches || []).map((match) => (
-
-
+
+
{formatFilters(
match.description.filters as Partial,
match.description.location as locationType,
optionIdsToLabels,
- )?.join(" • ")}
+ )?.join(' • ')}
{match.matches.map((p, i) => (
- {p.name} (
-
- @{p.username}
-
- )
- {i < match.matches.length - 1 && ", "}
-
+ {p.name} (
+
+ @{p.username}
+
+ ){i < match.matches.length - 1 && ', '}
+
))}
))}
-
-
- If someone resonates with you, reach out. A simple hello can be the
- start of a meaningful friendship, collaboration, or relationship.
+
+
+ If someone resonates with you, reach out. A simple hello can be the start of a
+ meaningful friendship, collaboration, or relationship.
Start a Conversation
-
- Compass is built and sustained by the community — no ads, no hidden
- algorithms, no subscriptions. Your presence and participation make it
- possible.
+
+ Compass is built and sustained by the community — no ads, no hidden algorithms, no
+ subscriptions. Your presence and participation make it possible.
-
+
-
)
}
const matchSamples = [
{
- "id": "ID search 1",
- "description": {
- "filters": {
- "orderBy": "created_time"
+ id: 'ID search 1',
+ description: {
+ filters: {
+ orderBy: 'created_time',
},
- "location": null
+ location: null,
},
- "matches": [
+ matches: [
{
- "name": "James Bond",
- "username": "jamesbond"
+ name: 'James Bond',
+ username: 'jamesbond',
},
{
- "name": "Lily",
- "username": "lilyrose"
- }
- ]
+ name: 'Lily',
+ username: 'lilyrose',
+ },
+ ],
},
{
- "id": "ID search 2",
- "description": {
- "filters": {
- "genders": [
- "female"
- ],
- "orderBy": "created_time"
+ id: 'ID search 2',
+ description: {
+ filters: {
+ genders: ['female'],
+ orderBy: 'created_time',
},
- "location": null
+ location: null,
},
- "matches": [
+ matches: [
{
- "name": "Lily",
- "username": "lilyrose"
- }
- ]
- }
+ name: 'Lily',
+ username: 'lilyrose',
+ },
+ ],
+ },
]
NewSearchAlertsEmail.PreviewProps = {
@@ -150,5 +144,4 @@ NewSearchAlertsEmail.PreviewProps = {
matches: matchSamples,
} as NewMessageEmailProps
-
export default NewSearchAlertsEmail
diff --git a/backend/email/emails/test.tsx b/backend/email/emails/test.tsx
index e7f74b05..46215e05 100644
--- a/backend/email/emails/test.tsx
+++ b/backend/email/emails/test.tsx
@@ -1,9 +1,9 @@
'use server'
-import { Head, Html, Preview, Tailwind, Text } from '@react-email/components'
+import {Head, Html, Preview, Tailwind, Text} from '@react-email/components'
import React from 'react'
-export const Test = (props: { name: string }) => {
+export const Test = (props: {name: string}) => {
return (
diff --git a/backend/email/emails/utils.tsx b/backend/email/emails/utils.tsx
index 4936ce5a..2386021e 100644
--- a/backend/email/emails/utils.tsx
+++ b/backend/email/emails/utils.tsx
@@ -1,73 +1,69 @@
-import React from 'react';
-import {Column, Img, Link, Row, Section, Text} from "@react-email/components";
-import {DOMAIN} from "common/envs/constants";
+import React from 'react'
+import {Column, Img, Link, Row, Section, Text} from '@react-email/components'
+import {DOMAIN} from 'common/envs/constants'
interface Props {
email?: string
unsubscribeUrl: string
}
-export const Footer = ({
- email,
- unsubscribeUrl,
- }: Props) => {
- return
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+export const Footer = ({email, unsubscribeUrl}: Props) => {
+ return (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
-
-
- © {new Date().getFullYear()} Compass
-
+
+
+ © {new Date().getFullYear()} Compass
+
-
- The email was sent to {email}. To no longer receive these emails, unsubscribe {' '}
-
- here
-
- .
-
-
-
+
+ The email was sent to {email}. To no longer receive these emails, unsubscribe{' '}
+ here.
+
+
+
+ )
}
export const footer = {
@@ -83,16 +79,14 @@ export const footerText = {
}
export const blackLinks = {
- color: 'black'
+ color: 'black',
}
-
// const footerLink = {
// color: 'inherit',
// textDecoration: 'none',
// }
-
export const main = {
// backgroundColor: '#f4f4f4',
fontFamily: 'Arial, sans-serif',
diff --git a/backend/email/emails/welcome.tsx b/backend/email/emails/welcome.tsx
index b231e33d..4802a285 100644
--- a/backend/email/emails/welcome.tsx
+++ b/backend/email/emails/welcome.tsx
@@ -1,8 +1,8 @@
-import React from 'react';
-import {Body, Button, Container, Head, Html, Preview, Section, Text,} from '@react-email/components'
+import React from 'react'
+import {Body, Button, Container, Head, Html, Preview, Section, Text} from '@react-email/components'
import {type User} from 'common/user'
-import {mockUser,} from './functions/mock'
-import {button, container, content, Footer, main, paragraph} from "email/utils";
+import {mockUser} from './functions/mock'
+import {button, container, content, Footer, main, paragraph} from 'email/utils'
// function randomHex(length: number) {
// const bytes = new Uint8Array(Math.ceil(length / 2));
@@ -20,11 +20,11 @@ interface WelcomeEmailProps {
}
export const WelcomeEmail = ({
- toUser,
- unsubscribeUrl,
- email,
- verificationLink,
- }: WelcomeEmailProps) => {
+ toUser,
+ unsubscribeUrl,
+ email,
+ verificationLink,
+}: WelcomeEmailProps) => {
const name = toUser.name.split(' ')[0]
// Some users may already have a verified email (e.g., signed it with Googl), but we send them a link anyway so that
@@ -34,7 +34,7 @@ export const WelcomeEmail = ({
return (
-
+
Welcome to Compass — Please confirm your email
@@ -42,37 +42,34 @@ export const WelcomeEmail = ({
Welcome to Compass, {name}!
- Compass is a free, community-owned platform built to help people form
- deep, meaningful connections — platonic, romantic, or collaborative.
- There are no ads, no hidden algorithms, and no subscriptions — just a
- transparent, open-source space shaped by people like you.
+ Compass is a free, community-owned platform built to help people form deep, meaningful
+ connections — platonic, romantic, or collaborative. There are no ads, no hidden
+ algorithms, and no subscriptions — just a transparent, open-source space shaped by
+ people like you.
- To finish creating your account and start exploring Compass, please
- confirm your email below:
+ To finish creating your account and start exploring Compass, please confirm your email
+ below:
-
+
Confirm My Email
-
- Or copy and paste this link into your browser:
+
+ Or copy and paste this link into your browser:
{verificationLink}
-
- Your presence and participation are what make Compass possible. Thank you
- for helping us build an internet space that prioritizes depth, trust, and
- community over monetization.
+
+ Your presence and participation are what make Compass possible. Thank you for helping
+ us build an internet space that prioritizes depth, trust, and community over
+ monetization.
-
+
@@ -85,5 +82,4 @@ WelcomeEmail.PreviewProps = {
unsubscribeUrl: 'https://compassmeet.com/unsubscribe',
} as WelcomeEmailProps
-
export default WelcomeEmail
diff --git a/backend/email/jest.config.js b/backend/email/jest.config.js
index 02a37b30..12d3b27e 100644
--- a/backend/email/jest.config.js
+++ b/backend/email/jest.config.js
@@ -1,31 +1,25 @@
module.exports = {
- preset: 'ts-jest',
- testEnvironment: 'node',
+ preset: 'ts-jest',
+ testEnvironment: 'node',
- rootDir: '.',
- testMatch: [
- "/tests/**/*.test.ts",
- "/tests/**/*.spec.ts"
- ],
+ rootDir: '.',
+ testMatch: ['/tests/**/*.test.ts', '/tests/**/*.spec.ts'],
- moduleNameMapper: {
- "^api/(.*)$": "/src/$1",
- "^shared/(.*)$": "/../shared/src/$1",
- "^common/(.*)$": "/../../common/src/$1",
- "^email/(.*)$": "/../email/emails/$1"
+ moduleNameMapper: {
+ '^api/(.*)$': '/src/$1',
+ '^shared/(.*)$': '/../shared/src/$1',
+ '^common/(.*)$': '/../../common/src/$1',
+ '^email/(.*)$': '/../email/emails/$1',
+ },
+
+ moduleFileExtensions: ['ts', 'js', 'json'],
+ clearMocks: true,
+
+ globals: {
+ 'ts-jest': {
+ tsconfig: '/tsconfig.test.json',
},
+ },
- moduleFileExtensions: ["ts", "js", "json"],
- clearMocks: true,
-
- globals: {
- 'ts-jest': {
- tsconfig: "/tsconfig.test.json"
- }
- },
-
- collectCoverageFrom: [
- "src/**/*.{ts,tsx}",
- "!src/**/*.d.ts"
- ],
-};
+ collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'],
+}
diff --git a/backend/email/knowledge.md b/backend/email/knowledge.md
index 6a8df322..e72d108a 100644
--- a/backend/email/knowledge.md
+++ b/backend/email/knowledge.md
@@ -19,7 +19,7 @@ The email module provides React Email components for sending beautiful, responsi
Import the helper functions from the email module to send emails:
```typescript
-import { sendNewEndorsementEmail } from 'email/functions/helpers'
+import {sendNewEndorsementEmail} from 'email/functions/helpers'
// Example usage
await sendNewEndorsementEmail(privateUser, creator, onUser, text)
diff --git a/backend/email/tsconfig.json b/backend/email/tsconfig.json
index 6a405478..ca1ef0fc 100644
--- a/backend/email/tsconfig.json
+++ b/backend/email/tsconfig.json
@@ -1,12 +1,9 @@
{
"compilerOptions": {
- "rootDir": "emails",
- "composite": true,
"module": "commonjs",
"moduleResolution": "node",
"noImplicitReturns": true,
"outDir": "lib",
- "tsBuildInfoFile": "lib/tsconfig.tsbuildinfo",
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
@@ -15,7 +12,6 @@
"allowJs": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
- "incremental": true,
"resolveJsonModule": true,
"isolatedModules": false,
"declaration": true,
@@ -29,6 +25,5 @@
"ts-node": {
"require": ["tsconfig-paths/register"]
},
- "references": [{ "path": "../../common" }, { "path": "../shared" }],
"include": ["emails/**/*.ts", "emails/**/*.tsx"]
}
diff --git a/backend/scripts/2025-04-23-migrate-social-links.ts b/backend/scripts/2025-04-23-migrate-social-links.ts
index 225c24e8..3b779a33 100644
--- a/backend/scripts/2025-04-23-migrate-social-links.ts
+++ b/backend/scripts/2025-04-23-migrate-social-links.ts
@@ -1,11 +1,11 @@
-import { removeUndefinedProps } from 'common/util/object'
-import { runScript } from './run-script'
-import { log } from 'shared/monitoring/log'
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { bulkUpdateData } from 'shared/supabase/utils'
-import { chunk } from 'lodash'
+import {removeUndefinedProps} from 'common/util/object'
+import {runScript} from './run-script'
+import {log} from 'shared/monitoring/log'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
+import {bulkUpdateData} from 'shared/supabase/utils'
+import {chunk} from 'lodash'
-runScript(async ({ pg }) => {
+runScript(async ({pg}) => {
const directClient = createSupabaseDirectClient()
// Get all users and their corresponding profiles
@@ -17,9 +17,9 @@ runScript(async ({ pg }) => {
log('Found', users.length, 'users to migrate')
- const updates = [] as { id: string; link: {} }[]
+ const updates = [] as {id: string; link: {}}[]
- for (const { id, data, twitter } of users) {
+ for (const {id, data, twitter} of users) {
const add = removeUndefinedProps({
discord: data.discordHandle,
manifold: data.manifoldHandle,
@@ -32,7 +32,7 @@ runScript(async ({ pg }) => {
})
if (Object.keys(add).length) {
- updates.push({ id, link: { ...add, ...(data.link || {}) } })
+ updates.push({id, link: {...add, ...(data.link || {})}})
}
}
@@ -54,6 +54,6 @@ runScript(async ({ pg }) => {
COALESCE((data -> 'link'), '{}'::jsonb),
true
)
- where data -> 'link' is null`
+ where data -> 'link' is null`,
)
})
diff --git a/backend/scripts/find-tiptap-nodes.ts b/backend/scripts/find-tiptap-nodes.ts
index eaacc7d9..0cead3f4 100644
--- a/backend/scripts/find-tiptap-nodes.ts
+++ b/backend/scripts/find-tiptap-nodes.ts
@@ -1,13 +1,8 @@
-import { runScript } from './run-script'
-import {
- renderSql,
- select,
- from,
- where,
-} from 'shared/supabase/sql-builder'
-import { SupabaseDirectClient } from 'shared/supabase/init'
+import {runScript} from './run-script'
+import {from, renderSql, select, where} from 'shared/supabase/sql-builder'
+import {SupabaseDirectClient} from 'shared/supabase/init'
-runScript(async ({ pg }) => {
+runScript(async ({pg}) => {
const tests = [
'mention',
'contract-mention',
@@ -28,7 +23,7 @@ const getNodes = async (pg: SupabaseDirectClient, nodeName: string) => {
const commentQuery = renderSql(
select('id, user_id, on_user_id, content'),
from('profile_comments'),
- where(`jsonb_path_exists(content, '$.**.type ? (@ == "${nodeName}")')`)
+ where(`jsonb_path_exists(content, '$.**.type ? (@ == "${nodeName}")')`),
)
const comments = await pg.manyOrNone(commentQuery)
@@ -44,7 +39,7 @@ const getNodes = async (pg: SupabaseDirectClient, nodeName: string) => {
const messageQuery = renderSql(
select('id, user_id, channel_id, content'),
from('private_user_messages'),
- where(`jsonb_path_exists(content, '$.**.type ? (@ == "${nodeName}")')`)
+ where(`jsonb_path_exists(content, '$.**.type ? (@ == "${nodeName}")')`),
)
const messages = await pg.manyOrNone(messageQuery)
@@ -60,7 +55,7 @@ const getNodes = async (pg: SupabaseDirectClient, nodeName: string) => {
const users = renderSql(
select('user_id, bio'),
from('profiles'),
- where(`jsonb_path_exists(bio::jsonb, '$.**.type ? (@ == "${nodeName}")')`)
+ where(`jsonb_path_exists(bio::jsonb, '$.**.type ? (@ == "${nodeName}")')`),
)
const usersWithMentions = await pg.manyOrNone(users)
diff --git a/backend/scripts/regen-schema.ts b/backend/scripts/regen-schema.ts
index 3e1320ee..b7e0fefc 100644
--- a/backend/scripts/regen-schema.ts
+++ b/backend/scripts/regen-schema.ts
@@ -1,17 +1,15 @@
import * as fs from 'fs/promises'
-import { execSync } from 'child_process'
-import { type SupabaseDirectClient } from 'shared/supabase/init'
-import { runScript } from 'run-script'
+import {execSync} from 'child_process'
+import {type SupabaseDirectClient} from 'shared/supabase/init'
+import {runScript} from 'run-script'
const outputDir = `../supabase/`
-runScript(async ({ pg }) => {
+runScript(async ({pg}) => {
// make the output directory if it doesn't exist
execSync(`mkdir -p ${outputDir}`)
// delete all sql files except seed.sql
- execSync(
- `cd ${outputDir} && find *.sql -type f ! -name seed.sql -delete || true`
- )
+ execSync(`cd ${outputDir} && find *.sql -type f ! -name seed.sql -delete || true`)
await generateSQLFiles(pg)
})
@@ -50,7 +48,7 @@ async function getTableInfo(pg: SupabaseDirectClient, tableName: string) {
AND d.adnum = a.attnum
WHERE table_schema = 'public' AND table_name = $1
ORDER BY column_name`,
- [tableName]
+ [tableName],
)
const checks = await pg.manyOrNone<{
@@ -68,7 +66,7 @@ async function getTableInfo(pg: SupabaseDirectClient, tableName: string) {
AND NOT cc.check_clause ilike '% IS NOT NULL'
AND tc.table_schema = 'public'
AND tc.table_name = $1`,
- [tableName]
+ [tableName],
)
const primaryKeys = await pg.map(
@@ -84,7 +82,7 @@ async function getTableInfo(pg: SupabaseDirectClient, tableName: string) {
AND ccu.column_name = c.column_name
WHERE constraint_type = 'PRIMARY KEY' AND tc.table_schema = 'public' AND tc.table_name = $1`,
[tableName],
- (row) => row.column_name as string
+ (row) => row.column_name as string,
)
const foreignKeys = await pg.manyOrNone<{
@@ -101,7 +99,7 @@ async function getTableInfo(pg: SupabaseDirectClient, tableName: string) {
WHERE
contype = 'f'
AND conrelid = $1::regclass`,
- [tableName]
+ [tableName],
)
const triggers = await pg.manyOrNone<{
@@ -116,13 +114,13 @@ async function getTableInfo(pg: SupabaseDirectClient, tableName: string) {
WHERE
tgrelid = $1::regclass
AND NOT tgisinternal`,
- [tableName]
+ [tableName],
)
const rlsEnabled = await pg.one(
`SELECT relrowsecurity
FROM pg_class
WHERE oid = $1::regclass`,
- [tableName]
+ [tableName],
)
const rls = !!rlsEnabled.relrowsecurity
@@ -144,7 +142,7 @@ async function getTableInfo(pg: SupabaseDirectClient, tableName: string) {
pg_policy
WHERE
polrelid = $1::regclass`,
- [tableName]
+ [tableName],
)
const indexes = await pg.manyOrNone<{
@@ -161,7 +159,7 @@ async function getTableInfo(pg: SupabaseDirectClient, tableName: string) {
AND tablename = $1
ORDER BY
indexname`,
- [tableName]
+ [tableName],
)
return {
@@ -190,20 +188,20 @@ async function getFunctions(pg: SupabaseDirectClient) {
WHERE
pronamespace = 'public'::regnamespace
and prokind = 'f'
- ORDER BY proname asc, pronargs asc, oid desc`
+ ORDER BY proname asc, pronargs asc, oid desc`,
)
return rows.filter((f) => !f.definition.includes(`'$libdir/`))
}
async function getViews(pg: SupabaseDirectClient) {
console.debug('Getting views')
- return pg.manyOrNone<{ view_name: string; definition: string }>(
+ return pg.manyOrNone<{view_name: string; definition: string}>(
`SELECT
table_name AS view_name,
view_definition AS definition
FROM information_schema.views
where table_schema = 'public'
- ORDER BY table_name asc`
+ ORDER BY table_name asc`,
)
}
@@ -211,13 +209,11 @@ async function generateSQLFiles(pg: SupabaseDirectClient) {
const tables = await pg.map(
"SELECT tablename FROM pg_tables WHERE schemaname = 'public'",
[],
- (row) => row.tablename as string
+ (row) => row.tablename as string,
)
console.debug(`Getting info for ${tables.length} tables`)
- const tableInfos = await Promise.all(
- tables.map((table) => getTableInfo(pg, table))
- )
+ const tableInfos = await Promise.all(tables.map((table) => getTableInfo(pg, table)))
const functions = await getFunctions(pg)
const views = await getViews(pg)
@@ -228,13 +224,11 @@ async function generateSQLFiles(pg: SupabaseDirectClient) {
// organize check constraints by column
const checksByColumn: {
- [col: string]: { name: string; definition: string }
+ [col: string]: {name: string; definition: string}
} = {}
const remainingChecks = []
for (const check of tableInfo.checks) {
- const matches = tableInfo.columns.filter((c) =>
- check.definition.includes(c.name)
- )
+ const matches = tableInfo.columns.filter((c) => check.definition.includes(c.name))
if (matches.length === 1) {
checksByColumn[matches[0].name] = check
@@ -260,8 +254,7 @@ async function generateSQLFiles(pg: SupabaseDirectClient) {
}
if (c.not_null) content += ' NOT NULL'
const check = checksByColumn[c.name]
- if (check)
- content += ` CONSTRAINT ${check.name} CHECK ${check.definition}`
+ if (check) content += ` CONSTRAINT ${check.name} CHECK ${check.definition}`
content += ',\n'
}
@@ -351,7 +344,5 @@ async function generateSQLFiles(pg: SupabaseDirectClient) {
await fs.writeFile(`${outputDir}/views.sql`, viewsContent)
console.debug('Prettifying SQL files...')
- execSync(
- `prettier --write ${outputDir}/*.sql --ignore-path ../supabase/.gitignore`
- )
+ execSync(`prettier --write ${outputDir}/*.sql --ignore-path ../supabase/.gitignore`)
}
diff --git a/backend/scripts/remove-tiptap-nodes.ts b/backend/scripts/remove-tiptap-nodes.ts
index 8231d6c1..6c170421 100644
--- a/backend/scripts/remove-tiptap-nodes.ts
+++ b/backend/scripts/remove-tiptap-nodes.ts
@@ -1,16 +1,8 @@
-import { runScript } from './run-script'
-import {
- renderSql,
- select,
- from,
- where,
-} from 'shared/supabase/sql-builder'
-import { type JSONContent } from '@tiptap/core'
+import {runScript} from './run-script'
+import {from, renderSql, select, where} from 'shared/supabase/sql-builder'
+import {type JSONContent} from '@tiptap/core'
-const removeNodesOfType = (
- content: JSONContent,
- typeToRemove: string
-): JSONContent | null => {
+const removeNodesOfType = (content: JSONContent, typeToRemove: string): JSONContent | null => {
if (content.type === typeToRemove) {
return null
}
@@ -20,21 +12,21 @@ const removeNodesOfType = (
.map((node) => removeNodesOfType(node, typeToRemove))
.filter((node) => node != null)
- return { ...content, content: newContent }
+ return {...content, content: newContent}
}
// No content to process, return node as is
return content
}
-runScript(async ({ pg }) => {
+runScript(async ({pg}) => {
const nodeType = 'linkPreview'
console.debug('\nSearching comments for linkPreviews...')
const commentQuery = renderSql(
select('id, content'),
from('profile_comments'),
- where(`jsonb_path_exists(content, '$.**.type ? (@ == "${nodeType}")')`)
+ where(`jsonb_path_exists(content, '$.**.type ? (@ == "${nodeType}")')`),
)
const comments = await pg.manyOrNone(commentQuery)
@@ -56,7 +48,7 @@ runScript(async ({ pg }) => {
const messageQuery = renderSql(
select('id, content'),
from('private_user_messages'),
- where(`jsonb_path_exists(content, '$.**.type ? (@ == "${nodeType}")')`)
+ where(`jsonb_path_exists(content, '$.**.type ? (@ == "${nodeType}")')`),
)
const messages = await pg.manyOrNone(messageQuery)
@@ -67,10 +59,10 @@ runScript(async ({ pg }) => {
console.debug('before', JSON.stringify(msg.content, null, 2))
console.debug('after', JSON.stringify(newContent, null, 2))
- await pg.none(
- 'update private_user_messages set content = $1 where id = $2',
- [newContent, msg.id]
- )
+ await pg.none('update private_user_messages set content = $1 where id = $2', [
+ newContent,
+ msg.id,
+ ])
console.debug('Updated message:', msg.id)
}
})
diff --git a/backend/scripts/run-script.ts b/backend/scripts/run-script.ts
index 693f59fd..fcb8a14e 100644
--- a/backend/scripts/run-script.ts
+++ b/backend/scripts/run-script.ts
@@ -1,9 +1,9 @@
import {initAdmin} from 'shared/init-admin'
-import {createSupabaseDirectClient, type SupabaseDirectClient,} from 'shared/supabase/init'
-import {refreshConfig} from "common/envs/prod";
+import {createSupabaseDirectClient, type SupabaseDirectClient} from 'shared/supabase/init'
+import {refreshConfig} from 'common/envs/prod'
export const runScript = async (
- main: (services: { pg: SupabaseDirectClient }) => Promise | any
+ main: (services: {pg: SupabaseDirectClient}) => Promise | any,
) => {
initAdmin()
await initEnvVariables()
@@ -16,9 +16,8 @@ export const runScript = async (
await main({pg})
}
-
export async function initEnvVariables() {
const {config} = await import('dotenv')
- config({ path: __dirname + '/../../.env' })
+ config({path: __dirname + '/../../.env'})
refreshConfig()
}
diff --git a/backend/scripts/tsconfig.json b/backend/scripts/tsconfig.json
index 10776c60..67819a59 100644
--- a/backend/scripts/tsconfig.json
+++ b/backend/scripts/tsconfig.json
@@ -20,10 +20,10 @@
"require": ["tsconfig-paths/register"]
},
"references": [
- { "path": "../../common" },
- { "path": "../shared" },
- { "path": "../api" },
- { "path": "../email" }
+ {"path": "../../common"},
+ {"path": "../shared"},
+ {"path": "../api"},
+ {"path": "../email"}
],
"compileOnSave": true
}
diff --git a/backend/shared/.eslintrc.js b/backend/shared/.eslintrc.js
index 9d57eb6c..95e46d95 100644
--- a/backend/shared/.eslintrc.js
+++ b/backend/shared/.eslintrc.js
@@ -1,7 +1,7 @@
module.exports = {
- plugins: ['lodash', 'unused-imports'],
+ plugins: ['lodash', 'unused-imports', 'simple-import-sort'],
extends: ['eslint:recommended'],
- ignorePatterns: ['dist', 'lib', 'coverage', 'tests'],
+ ignorePatterns: ['dist', 'lib', 'coverage'],
env: {
node: true,
},
@@ -16,9 +16,9 @@ module.exports = {
project: ['./tsconfig.json', './tsconfig.test.json'],
},
rules: {
- "@typescript-eslint/no-empty-object-type": "error", // replaces banning {}
- "@typescript-eslint/no-unsafe-function-type": "error", // replaces banning Function
- "@typescript-eslint/no-wrapper-object-types": "error", // replaces banning String, Number, etc.
+ '@typescript-eslint/no-empty-object-type': 'error', // replaces banning {}
+ '@typescript-eslint/no-unsafe-function-type': 'error', // replaces banning Function
+ '@typescript-eslint/no-wrapper-object-types': 'error', // replaces banning String, Number, etc.
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-extra-semi': 'off',
'@typescript-eslint/no-unused-vars': [
@@ -35,10 +35,9 @@ module.exports = {
},
],
rules: {
- 'linebreak-style': [
- 'error',
- process.platform === 'win32' ? 'windows' : 'unix',
- ],
+ 'linebreak-style': ['error', process.platform === 'win32' ? 'windows' : 'unix'],
'lodash/import-scope': [2, 'member'],
+ 'simple-import-sort/imports': 'error',
+ 'simple-import-sort/exports': 'error',
},
}
diff --git a/backend/shared/.prettierignore b/backend/shared/.prettierignore
new file mode 100644
index 00000000..d4cf806b
--- /dev/null
+++ b/backend/shared/.prettierignore
@@ -0,0 +1,34 @@
+# Dependencies
+node_modules
+.yarn
+
+# Build outputs
+dist
+build
+.next
+out
+lib
+
+# Generated files
+coverage
+*.min.js
+*.min.css
+
+# Database / migrations
+**/*.sql
+
+# Config / lock files
+yarn.lock
+package-lock.json
+pnpm-lock.yaml
+
+# Android / iOS
+android
+ios
+capacitor.config.ts
+
+# Playwright
+tests/reports
+playwright-report
+
+coverage
\ No newline at end of file
diff --git a/backend/shared/jest.config.js b/backend/shared/jest.config.js
index 35eecc51..b8f02cb2 100644
--- a/backend/shared/jest.config.js
+++ b/backend/shared/jest.config.js
@@ -1,31 +1,25 @@
module.exports = {
- preset: 'ts-jest',
- testEnvironment: 'node',
+ preset: 'ts-jest',
+ testEnvironment: 'node',
- rootDir: '.',
- testMatch: [
- "/tests/**/*.test.ts",
- "/tests/**/*.spec.ts"
- ],
+ rootDir: '.',
+ testMatch: ['/tests/**/*.test.ts', '/tests/**/*.spec.ts'],
- moduleNameMapper: {
- "^api/(.*)$": "/../api/src/$1",
- "^shared/(.*)$": "/src/$1",
- "^common/(.*)$": "/../../common/src/$1",
- "^email/(.*)$": "/../email/emails/$1"
+ moduleNameMapper: {
+ '^api/(.*)$': '/../api/src/$1',
+ '^shared/(.*)$': '/src/$1',
+ '^common/(.*)$': '/../../common/src/$1',
+ '^email/(.*)$': '/../email/emails/$1',
+ },
+
+ moduleFileExtensions: ['ts', 'js', 'json'],
+ clearMocks: true,
+
+ globals: {
+ 'ts-jest': {
+ tsconfig: '/tsconfig.test.json',
},
+ },
- moduleFileExtensions: ["ts", "js", "json"],
- clearMocks: true,
-
- globals: {
- 'ts-jest': {
- tsconfig: "/tsconfig.test.json"
- }
- },
-
- collectCoverageFrom: [
- "src/**/*.{ts,tsx}",
- "!src/**/*.d.ts"
- ],
-};
+ collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'],
+}
diff --git a/backend/shared/package.json b/backend/shared/package.json
index a5c49104..e234b775 100644
--- a/backend/shared/package.json
+++ b/backend/shared/package.json
@@ -5,10 +5,9 @@
"scripts": {
"build": "tsc -b && yarn --cwd=../../common tsc-alias && tsc-alias",
"compile": "tsc -b",
- "verify": "yarn --cwd=../.. verify",
"lint": "npx eslint . --max-warnings 0",
"lint-fix": "npx eslint . --fix",
- "verify:dir": "npx eslint . --max-warnings 0",
+ "typecheck": "npx tsc --noEmit",
"test": "jest --config jest.config.js --passWithNoTests"
},
"sideEffects": false,
diff --git a/backend/shared/src/analytics.ts b/backend/shared/src/analytics.ts
index 4c8615ff..18cc2f3b 100644
--- a/backend/shared/src/analytics.ts
+++ b/backend/shared/src/analytics.ts
@@ -1,8 +1,8 @@
+import {ENV_CONFIG} from 'common/envs/constants'
import {Request} from 'express'
-import {trackAuditEvent} from 'shared/audit-events'
import {PostHog} from 'posthog-node'
+import {trackAuditEvent} from 'shared/audit-events'
import {log} from 'shared/utils'
-import {ENV_CONFIG} from "common/envs/constants";
const key = ENV_CONFIG.posthogKey
@@ -12,11 +12,7 @@ const client = new PostHog(key, {
flushInterval: 0,
})
-export const track = async (
- userId: string,
- eventName: string,
- properties?: any
-) => {
+export const track = async (userId: string, eventName: string, properties?: any) => {
try {
client.capture({
distinctId: userId,
@@ -28,11 +24,7 @@ export const track = async (
}
}
-export const trackPublicEvent = async (
- userId: string,
- eventName: string,
- properties?: any
-) => {
+export const trackPublicEvent = async (userId: string, eventName: string, properties?: any) => {
const allProperties = Object.assign(properties ?? {}, {})
const {commentId, ...data} = allProperties
try {
diff --git a/backend/shared/src/audit-events.ts b/backend/shared/src/audit-events.ts
index d116012c..85d84999 100644
--- a/backend/shared/src/audit-events.ts
+++ b/backend/shared/src/audit-events.ts
@@ -1,18 +1,18 @@
-import { createSupabaseDirectClient } from 'shared/supabase/init'
-import { tryOrLogError } from 'shared/helpers/try-or-log-error'
+import {tryOrLogError} from 'shared/helpers/try-or-log-error'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
export const trackAuditEvent = async (
userId: string,
name: string,
commentId?: string,
- otherProps?: Record
+ otherProps?: Record,
) => {
const pg = createSupabaseDirectClient()
return await tryOrLogError(
pg.none(
`insert into audit_events (name, user_id, contract_id, comment_id, data)
values ($1, $2, '', $3, $4) on conflict do nothing`,
- [name, userId, commentId, otherProps]
- )
+ [name, userId, commentId, otherProps],
+ ),
)
}
diff --git a/backend/shared/src/compatibility/compute-scores.ts b/backend/shared/src/compatibility/compute-scores.ts
index bc698a42..73bfaa8b 100644
--- a/backend/shared/src/compatibility/compute-scores.ts
+++ b/backend/shared/src/compatibility/compute-scores.ts
@@ -1,17 +1,18 @@
-import {createSupabaseDirectClient, SupabaseDirectClient} from 'shared/supabase/init'
+import {hrtime} from 'node:process'
+
import {getCompatibilityScore, hasAnsweredQuestions} from 'common/profiles/compatibility-score'
+import {groupBy} from 'lodash'
import {
getAnswersForUser,
getCompatibilityAnswers,
getGenderCompatibleProfiles,
- getProfile
-} from "shared/profiles/supabase"
-import {groupBy} from "lodash"
-import {hrtime} from "node:process"
+ getProfile,
+} from 'shared/profiles/supabase'
+import {createSupabaseDirectClient, SupabaseDirectClient} from 'shared/supabase/init'
// Canonicalize pair ordering (user_id_1 < user_id_2 lexicographically)
function canonicalPair(a: string, b: string) {
- return a < b ? [a, b] as const : [b, a] as const
+ return a < b ? ([a, b] as const) : ([b, a] as const)
}
export async function recomputeCompatibilityScoresForUser(
@@ -25,7 +26,7 @@ export async function recomputeCompatibilityScoresForUser(
if (!profile) throw new Error(`Profile not found for user ${userId}`)
// Load all answers for the target user
- const answersSelf = await getAnswersForUser(userId);
+ const answersSelf = await getAnswersForUser(userId)
// If the user has no answered questions, set the score to null
if (!hasAnsweredQuestions(answersSelf)) {
@@ -34,7 +35,7 @@ export async function recomputeCompatibilityScoresForUser(
set score = null
where user_id_1 = $1
or user_id_2 = $1`,
- [userId]
+ [userId],
)
return
}
@@ -43,7 +44,9 @@ export async function recomputeCompatibilityScoresForUser(
const profileAnswers = await getCompatibilityAnswers([userId, ...otherUserIds])
const answersByUser = groupBy(profileAnswers, 'creator_id')
- console.log(`Recomputing compatibility scores for user ${userId}, ${otherUserIds.length} other users.`)
+ console.log(
+ `Recomputing compatibility scores for user ${userId}, ${otherUserIds.length} other users.`,
+ )
const rows = []
@@ -60,11 +63,9 @@ export async function recomputeCompatibilityScoresForUser(
if (rows.length === 0) return
- const values = rows
- .map((_, i) => `($${i * 3 + 1}, $${i * 3 + 2}, $${i * 3 + 3})`)
- .join(", ");
+ const values = rows.map((_, i) => `($${i * 3 + 1}, $${i * 3 + 2}, $${i * 3 + 3})`).join(', ')
- const flatParams = rows.flat();
+ const flatParams = rows.flat()
// Upsert scores for each pair
await pg.none(
@@ -75,8 +76,8 @@ export async function recomputeCompatibilityScoresForUser(
ON CONFLICT (user_id_1, user_id_2)
DO UPDATE SET score = EXCLUDED.score
`,
- flatParams
- );
+ flatParams,
+ )
//
// for (const otherId of otherUserIds) {
diff --git a/backend/shared/src/constants.ts b/backend/shared/src/constants.ts
index 71c07686..f206b262 100644
--- a/backend/shared/src/constants.ts
+++ b/backend/shared/src/constants.ts
@@ -1,3 +1,3 @@
export const getLocalEnv = () => {
return (process.env.ENVIRONMENT?.toUpperCase() ?? 'DEV') as 'PROD' | 'DEV'
-}
\ No newline at end of file
+}
diff --git a/backend/shared/src/create-profile-notification.ts b/backend/shared/src/create-profile-notification.ts
index 85e23a6f..de296ba9 100644
--- a/backend/shared/src/create-profile-notification.ts
+++ b/backend/shared/src/create-profile-notification.ts
@@ -1,13 +1,14 @@
-import { Row } from 'common/supabase/utils'
-import { getPrivateUser, getUser } from './utils'
-import { createSupabaseDirectClient } from './supabase/init'
-import { getNotificationDestinationsForUser } from 'common/user-notification-preferences'
-import { Notification } from 'common/notifications'
-import { insertNotificationToSupabase } from './supabase/notifications'
-import { getProfile } from 'shared/profiles/supabase'
+import {Notification} from 'common/notifications'
+import {Row} from 'common/supabase/utils'
+import {getNotificationDestinationsForUser} from 'common/user-notification-preferences'
+import {getProfile} from 'shared/profiles/supabase'
+
+import {createSupabaseDirectClient} from './supabase/init'
+import {insertNotificationToSupabase} from './supabase/notifications'
+import {getPrivateUser, getUser} from './utils'
export const createProfileLikeNotification = async (like: Row<'profile_likes'>) => {
- const { creator_id, target_id, like_id } = like
+ const {creator_id, target_id, like_id} = like
const pg = createSupabaseDirectClient()
const targetPrivateUser = await getPrivateUser(target_id)
@@ -15,10 +16,7 @@ export const createProfileLikeNotification = async (like: Row<'profile_likes'>)
if (!targetPrivateUser || !profile) return
- const { sendToBrowser } = getNotificationDestinationsForUser(
- targetPrivateUser,
- 'new_profile_like'
- )
+ const {sendToBrowser} = getNotificationDestinationsForUser(targetPrivateUser, 'new_profile_like')
if (!sendToBrowser) return
const id = `${creator_id}-${like_id}`
@@ -41,9 +39,9 @@ export const createProfileLikeNotification = async (like: Row<'profile_likes'>)
export const createProfileShipNotification = async (
ship: Row<'profile_ships'>,
- recipientId: string
+ recipientId: string,
) => {
- const { creator_id, target1_id, target2_id, ship_id } = ship
+ const {creator_id, target1_id, target2_id, ship_id} = ship
const otherTargetId = target1_id === recipientId ? target2_id : target1_id
const creator = await getUser(creator_id)
@@ -60,10 +58,7 @@ export const createProfileShipNotification = async (
return
}
- const { sendToBrowser } = getNotificationDestinationsForUser(
- targetPrivateUser,
- 'new_profile_ship'
- )
+ const {sendToBrowser} = getNotificationDestinationsForUser(targetPrivateUser, 'new_profile_ship')
if (!sendToBrowser) return
const id = `${creator_id}-${ship_id}`
diff --git a/backend/shared/src/encryption.ts b/backend/shared/src/encryption.ts
index aeb6b03c..8f744627 100644
--- a/backend/shared/src/encryption.ts
+++ b/backend/shared/src/encryption.ts
@@ -1,5 +1,5 @@
-import crypto from "crypto";
-import {ENV_CONFIG} from "common/envs/constants";
+import {ENV_CONFIG} from 'common/envs/constants'
+import crypto from 'crypto'
/**
* MASTER_KEY must be a 32-byte Buffer (AES-256).
@@ -11,11 +11,11 @@ const getMasterKey = () => {
if (ENV_CONFIG.dbEncryptionKey) {
const MASTER_KEY_BASE64 = ENV_CONFIG.dbEncryptionKey
- _MASTER_KEY = Buffer.from(MASTER_KEY_BASE64, "base64")
- if (_MASTER_KEY.length !== 32) throw new Error("MASTER_KEY must be 32 bytes")
+ _MASTER_KEY = Buffer.from(MASTER_KEY_BASE64, 'base64')
+ if (_MASTER_KEY.length !== 32) throw new Error('MASTER_KEY must be 32 bytes')
}
- if (!_MASTER_KEY) throw new Error("MASTER_KEY not set")
+ if (!_MASTER_KEY) throw new Error('MASTER_KEY not set')
return _MASTER_KEY
}
@@ -27,32 +27,40 @@ const getMasterKey = () => {
* It's used to prove the authenticity and integrity of a message
*/
export function encryptMessage(plaintext: string) {
- const iv = crypto.randomBytes(12); // 96-bit IV, recommended for AES-GCM
- const cipher = crypto.createCipheriv("aes-256-gcm", getMasterKey(), iv);
- const ciphertext = Buffer.concat([cipher.update(plaintext, "utf8"), cipher.final()]);
- const tag = cipher.getAuthTag();
+ const iv = crypto.randomBytes(12) // 96-bit IV, recommended for AES-GCM
+ const cipher = crypto.createCipheriv('aes-256-gcm', getMasterKey(), iv)
+ const ciphertext = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()])
+ const tag = cipher.getAuthTag()
// console.debug(plaintext, iv, ciphertext, tag)
return {
- ciphertext: ciphertext.toString("base64"),
- iv: iv.toString("base64"),
- tag: tag.toString("base64"),
- };
+ ciphertext: ciphertext.toString('base64'),
+ iv: iv.toString('base64'),
+ tag: tag.toString('base64'),
+ }
}
/**
* Decrypt base64 ciphertext + iv + tag -> plaintext string.
* Throws on auth failure.
*/
-export function decryptMessage({ciphertext, iv, tag}: { ciphertext: string; iv: string; tag: string; }) {
- const ivBuf = Buffer.from(iv, "base64");
- const ctBuf = Buffer.from(ciphertext, "base64");
- const tagBuf = Buffer.from(tag, "base64");
+export function decryptMessage({
+ ciphertext,
+ iv,
+ tag,
+}: {
+ ciphertext: string
+ iv: string
+ tag: string
+}) {
+ const ivBuf = Buffer.from(iv, 'base64')
+ const ctBuf = Buffer.from(ciphertext, 'base64')
+ const tagBuf = Buffer.from(tag, 'base64')
- const decipher = crypto.createDecipheriv("aes-256-gcm", getMasterKey(), ivBuf);
- decipher.setAuthTag(tagBuf);
- const plaintext = Buffer.concat([decipher.update(ctBuf), decipher.final()]).toString("utf8");
+ const decipher = crypto.createDecipheriv('aes-256-gcm', getMasterKey(), ivBuf)
+ decipher.setAuthTag(tagBuf)
+ const plaintext = Buffer.concat([decipher.update(ctBuf), decipher.final()]).toString('utf8')
// console.debug("Decrypted message:", plaintext);
- return plaintext;
-}
\ No newline at end of file
+ return plaintext
+}
diff --git a/backend/shared/src/firebase-utils.ts b/backend/shared/src/firebase-utils.ts
index 5d9ebb33..5b65f546 100644
--- a/backend/shared/src/firebase-utils.ts
+++ b/backend/shared/src/firebase-utils.ts
@@ -1,7 +1,6 @@
-import {readFileSync} from "fs";
+import {ENV_CONFIG, getStorageBucketId} from 'common/envs/constants'
import {getStorage, Storage} from 'firebase-admin/storage'
-
-import {ENV_CONFIG, getStorageBucketId} from "common/envs/constants";
+import {readFileSync} from 'fs'
export const getServiceAccountCredentials = () => {
let keyPath = ENV_CONFIG.googleApplicationCredentials
@@ -30,7 +29,6 @@ export function getBucket() {
return getStorage().bucket(getStorageBucketId())
}
-
export type Bucket = ReturnType['bucket']>
export async function deleteUserFiles(username: string) {
@@ -38,14 +36,13 @@ export async function deleteUserFiles(username: string) {
// Delete all files in the directory
const bucket = getBucket()
- const [files] = await bucket.getFiles({prefix: path});
+ const [files] = await bucket.getFiles({prefix: path})
if (files.length === 0) {
- console.debug(`No files found in bucket for user ${username}`);
- return;
+ console.debug(`No files found in bucket for user ${username}`)
+ return
}
- await Promise.all(files.map(file => file.delete()));
- console.debug(`Deleted ${files.length} files for user ${username}`);
-
-}
\ No newline at end of file
+ await Promise.all(files.map((file) => file.delete()))
+ console.debug(`Deleted ${files.length} files for user ${username}`)
+}
diff --git a/backend/shared/src/helpers/auth.ts b/backend/shared/src/helpers/auth.ts
index 240efdc1..2f31b303 100644
--- a/backend/shared/src/helpers/auth.ts
+++ b/backend/shared/src/helpers/auth.ts
@@ -1,11 +1,8 @@
-import { APIError } from 'common/api/utils'
-import { isAdminId, isModId } from 'common/envs/constants'
+import {APIError} from 'common/api/utils'
+import {isAdminId, isModId} from 'common/envs/constants'
export const throwErrorIfNotMod = async (userId: string) => {
if (!isAdminId(userId) && !isModId(userId)) {
- throw new APIError(
- 403,
- `User ${userId} must be an admin or trusted to perform this action.`
- )
+ throw new APIError(403, `User ${userId} must be an admin or trusted to perform this action.`)
}
}
diff --git a/backend/shared/src/helpers/generate-and-update-avatar-urls.ts b/backend/shared/src/helpers/generate-and-update-avatar-urls.ts
index 12582548..be1ee12d 100644
--- a/backend/shared/src/helpers/generate-and-update-avatar-urls.ts
+++ b/backend/shared/src/helpers/generate-and-update-avatar-urls.ts
@@ -1,11 +1,7 @@
import {DOMAIN} from 'common/envs/constants'
-import {Bucket} from "shared/firebase-utils";
+import {Bucket} from 'shared/firebase-utils'
-export const generateAvatarUrl = async (
- userId: string,
- name: string,
- bucket: Bucket
-) => {
+export const generateAvatarUrl = async (userId: string, name: string, bucket: Bucket) => {
const backgroundColors = [
'#FF8C00',
'#800080',
@@ -16,9 +12,9 @@ export const generateAvatarUrl = async (
'#008080',
]
const imageUrl = `https://ui-avatars.com/api/?name=${encodeURIComponent(
- name
+ name,
)}&background=${encodeURIComponent(
- backgroundColors[Math.floor(Math.random() * backgroundColors.length)]
+ backgroundColors[Math.floor(Math.random() * backgroundColors.length)],
)}&color=fff&size=256&format=png`
try {
const res = await fetch(imageUrl)
diff --git a/backend/shared/src/helpers/try-or-log-error.ts b/backend/shared/src/helpers/try-or-log-error.ts
index 244bdfdd..f4f8b206 100644
--- a/backend/shared/src/helpers/try-or-log-error.ts
+++ b/backend/shared/src/helpers/try-or-log-error.ts
@@ -1,4 +1,4 @@
-import { log } from 'shared/utils'
+import {log} from 'shared/utils'
export const tryOrLogError = async (task: Promise) => {
try {
diff --git a/backend/shared/src/init-admin.ts b/backend/shared/src/init-admin.ts
index 267e77e5..23220efd 100644
--- a/backend/shared/src/init-admin.ts
+++ b/backend/shared/src/init-admin.ts
@@ -1,15 +1,14 @@
+import {IS_FIREBASE_EMULATOR} from 'common/envs/constants'
+import {IS_LOCAL} from 'common/hosting/constants'
import * as admin from 'firebase-admin'
-import {getServiceAccountCredentials} from "shared/firebase-utils";
-import {IS_LOCAL} from "common/hosting/constants";
-import {IS_FIREBASE_EMULATOR} from "common/envs/constants";
+import {getServiceAccountCredentials} from 'shared/firebase-utils'
export const initAdmin = () => {
-
if (IS_LOCAL && IS_FIREBASE_EMULATOR) {
// console.log("Using Firebase Emulator Suite.")
return admin.initializeApp({
- projectId: "compass-57c3c",
- storageBucket: "compass-130ba-public",
+ projectId: 'compass-57c3c',
+ storageBucket: 'compass-130ba-public',
})
}
@@ -34,4 +33,4 @@ export const initAdmin = () => {
console.debug(`Initializing connection to default Firebase...`)
return admin.initializeApp()
-}
\ No newline at end of file
+}
diff --git a/backend/shared/src/monitoring/context.ts b/backend/shared/src/monitoring/context.ts
index e7a3905a..5853d6a1 100644
--- a/backend/shared/src/monitoring/context.ts
+++ b/backend/shared/src/monitoring/context.ts
@@ -1,7 +1,7 @@
// Shared contextual information that metrics and logging can use, e.g.
// the scheduler job or HTTP request endpoint currently running.
-import { AsyncLocalStorage } from 'node:async_hooks'
+import {AsyncLocalStorage} from 'node:async_hooks'
export type ContextDetails = Record
diff --git a/backend/shared/src/monitoring/instance-info.ts b/backend/shared/src/monitoring/instance-info.ts
index 366c0360..0096f6b1 100644
--- a/backend/shared/src/monitoring/instance-info.ts
+++ b/backend/shared/src/monitoring/instance-info.ts
@@ -1,5 +1,5 @@
-import { last } from 'lodash'
import * as metadata from 'gcp-metadata'
+import {last} from 'lodash'
export type InstanceInfo = {
projectId: string
@@ -16,5 +16,5 @@ export async function getInstanceInfo() {
])
// GCP returns zone as `projects/${id}/zones/${zone}
const zone = last(fqZone.split('/'))
- return { projectId, instanceId, zone } as InstanceInfo
+ return {projectId, instanceId, zone} as InstanceInfo
}
diff --git a/backend/shared/src/monitoring/log.ts b/backend/shared/src/monitoring/log.ts
index 1388a151..b142eb75 100644
--- a/backend/shared/src/monitoring/log.ts
+++ b/backend/shared/src/monitoring/log.ts
@@ -1,8 +1,10 @@
-import { format } from 'node:util'
-import { isError, pick, omit } from 'lodash'
-import { dim, red, yellow } from 'colors/safe'
-import { getMonitoringContext } from './context'
-import {IS_GOOGLE_CLOUD} from "common/hosting/constants";
+import {format} from 'node:util'
+
+import {dim, red, yellow} from 'colors/safe'
+import {IS_GOOGLE_CLOUD} from 'common/hosting/constants'
+import {isError, omit, pick} from 'lodash'
+
+import {getMonitoringContext} from './context'
// mapping JS log levels (e.g. functions on console object) to GCP log levels
const JS_TO_GCP_LEVELS = {
@@ -62,19 +64,15 @@ function ts() {
// handles both the cases where someone wants to write unstructured
// stream-of-consciousness console logging like log('count:', 1, 'user': u)
// and also structured key/value logging with severity
-function writeLog(
- level: LogLevel,
- msg: unknown,
- opts?: { props?: LogDetails; rest?: unknown[] }
-) {
+function writeLog(level: LogLevel, msg: unknown, opts?: {props?: LogDetails; rest?: unknown[]}) {
try {
- const { props, rest } = opts ?? {}
+ const {props, rest} = opts ?? {}
const contextData = getMonitoringContext()
const message = format(toString(msg), ...(rest ?? []))
- const data = { ...(contextData ?? {}), ...(props ?? {}) }
+ const data = {...(contextData ?? {}), ...(props ?? {})}
if (IS_GOOGLE_CLOUD) {
const severity = JS_TO_GCP_LEVELS[level]
- const output: LogDetails = { severity, message, ...data }
+ const output: LogDetails = {severity, message, ...data}
if (msg instanceof Error) {
// record error properties in GCP if you just do log(err)
output['error'] = msg
@@ -84,7 +82,7 @@ function writeLog(
const category = Object.values(pick(data, DISPLAY_CATEGORY_KEYS)).join()
const categoryLabel = category ? dim(category) + ' ' : ''
const details = Object.entries(
- omit(data, [...DISPLAY_CATEGORY_KEYS, ...DISPLAY_EXCLUDED_KEYS])
+ omit(data, [...DISPLAY_CATEGORY_KEYS, ...DISPLAY_EXCLUDED_KEYS]),
).map(([key, value]) => `\n ${key}: ${JSON.stringify(value)}`)
const result = `${dim(ts())} ${categoryLabel}${message}${details}`
if (level === 'error') {
@@ -104,10 +102,9 @@ function writeLog(
export function getLogger(): Logger {
const logger = ((msg: unknown, ...rest: unknown[]) =>
- writeLog(DEFAULT_LEVEL, msg, { rest })) as Logger
+ writeLog(DEFAULT_LEVEL, msg, {rest})) as Logger
for (const level of JS_LEVELS) {
- logger[level] = (msg: unknown, props?: LogDetails) =>
- writeLog(level, msg, { props })
+ logger[level] = (msg: unknown, props?: LogDetails) => writeLog(level, msg, {props})
}
return logger
}
diff --git a/backend/shared/src/monitoring/metric-writer.ts b/backend/shared/src/monitoring/metric-writer.ts
index d35e458c..2889784b 100644
--- a/backend/shared/src/monitoring/metric-writer.ts
+++ b/backend/shared/src/monitoring/metric-writer.ts
@@ -1,10 +1,11 @@
import {MetricServiceClient} from '@google-cloud/monitoring'
+import {IS_GOOGLE_CLOUD} from 'common/hosting/constants'
import {average, sumOfSquaredError} from 'common/util/math'
-import {log} from './log'
-import {getInstanceInfo, InstanceInfo} from './instance-info'
import {chunk} from 'lodash'
-import {CUSTOM_METRICS, metrics, MetricStore, MetricStoreEntry,} from './metrics'
-import {IS_GOOGLE_CLOUD} from "common/hosting/constants";
+
+import {getInstanceInfo, InstanceInfo} from './instance-info'
+import {log} from './log'
+import {CUSTOM_METRICS, metrics, MetricStore, MetricStoreEntry} from './metrics'
// how often metrics are written. GCP says don't write for a single time series
// more than once per 5 seconds.
@@ -56,11 +57,7 @@ function serializeValue(entry: MetricStoreEntry) {
}
// see https://cloud.google.com/monitoring/api/ref_v3/rest/v3/TimeSeries
-function serializeEntries(
- instance: InstanceInfo,
- entries: MetricStoreEntry[],
- ts: number
-) {
+function serializeEntries(instance: InstanceInfo, entries: MetricStoreEntry[], ts: number) {
return entries.map((entry) => ({
metricKind: CUSTOM_METRICS[entry.type].metricKind,
resource: {
diff --git a/backend/shared/src/monitoring/metrics.ts b/backend/shared/src/monitoring/metrics.ts
index b97fd3ce..fbc61ac2 100644
--- a/backend/shared/src/monitoring/metrics.ts
+++ b/backend/shared/src/monitoring/metrics.ts
@@ -1,4 +1,4 @@
-import { isEqual, flatten } from 'lodash'
+import {flatten, isEqual} from 'lodash'
// see https://cloud.google.com/monitoring/api/ref_v3/rest/v3/projects.metricDescriptors#MetricKind
export type MetricKind = 'GAUGE' | 'CUMULATIVE'
@@ -83,7 +83,7 @@ export const CUSTOM_METRICS = {
metricKind: 'CUMULATIVE',
valueKind: 'int64Value',
},
-} as const satisfies { [k: string]: MetricDescriptor }
+} as const satisfies {[k: string]: MetricDescriptor}
// the typing for all this could be way fancier, but seems overkill
@@ -135,16 +135,14 @@ export class MetricStore {
}
freshEntries() {
- return flatten(
- Array.from(this.data.entries(), ([_, vs]) => vs.filter((e) => e.fresh))
- )
+ return flatten(Array.from(this.data.entries(), ([_, vs]) => vs.filter((e) => e.fresh)))
}
// mqp: we could clear all gauges but then we should centralize the process for polling
// them in order to not have weird gaps.
clearDistributionGauges() {
for (const k of this.data.keys()) {
- const { metricKind, valueKind } = CUSTOM_METRICS[k]
+ const {metricKind, valueKind} = CUSTOM_METRICS[k]
if (metricKind === 'GAUGE' && valueKind === 'distributionValue') {
this.data.delete(k)
}
@@ -162,7 +160,7 @@ export class MetricStore {
}
}
// none exists, so create it
- const entry = { type, labels, startTime: Date.now(), fresh: true, value: 0 }
+ const entry = {type, labels, startTime: Date.now(), fresh: true, value: 0}
entries.push(entry)
return entry as MetricStoreEntry
}
diff --git a/backend/shared/src/profiles/parse-photos.ts b/backend/shared/src/profiles/parse-photos.ts
index 77adc71a..f9ad4a6d 100644
--- a/backend/shared/src/profiles/parse-photos.ts
+++ b/backend/shared/src/profiles/parse-photos.ts
@@ -4,7 +4,7 @@ export const removePinnedUrlFromPhotoUrls = async (parsedBody: {
}) => {
if (parsedBody.photo_urls && parsedBody.pinned_url) {
parsedBody.photo_urls = parsedBody.photo_urls.filter(
- (url: string) => url !== parsedBody.pinned_url
+ (url: string) => url !== parsedBody.pinned_url,
)
}
}
diff --git a/backend/shared/src/profiles/supabase.ts b/backend/shared/src/profiles/supabase.ts
index 0d8d6339..a72d8676 100644
--- a/backend/shared/src/profiles/supabase.ts
+++ b/backend/shared/src/profiles/supabase.ts
@@ -1,7 +1,7 @@
import {areGenderCompatible} from 'common/profiles/compatibility-util'
import {type Profile, type ProfileRow} from 'common/profiles/profile'
-import {type User} from 'common/user'
import {Row} from 'common/supabase/utils'
+import {type User} from 'common/user'
import {createSupabaseDirectClient} from 'shared/supabase/init'
export type ProfileAndUserRow = ProfileRow & {
@@ -37,7 +37,7 @@ export const getProfile = async (userId: string) => {
where user_id = $1
`,
[userId],
- convertRow
+ convertRow,
)
}
@@ -52,7 +52,7 @@ export const getProfiles = async (userIds: string[]) => {
where user_id = any ($1)
`,
[userIds],
- convertRow
+ convertRow,
)
}
@@ -71,15 +71,12 @@ export const getGenderCompatibleProfiles = async (profile: ProfileRow) => {
and profiles.pinned_url is not null
`,
{...profile},
- convertRow
+ convertRow,
)
return profiles.filter((l: Profile) => areGenderCompatible(profile, l))
}
-export const getCompatibleProfiles = async (
- profile: ProfileRow,
- radiusKm: number | undefined
-) => {
+export const getCompatibleProfiles = async (profile: ProfileRow, radiusKm: number | undefined) => {
const pg = createSupabaseDirectClient()
return await pg.map(
`
@@ -107,7 +104,7 @@ export const getCompatibleProfiles = async (
profiles.city_longitude) < $(radiusKm)
`,
{...profile, radiusKm: radiusKm ?? 40_000},
- convertRow
+ convertRow,
)
}
@@ -119,7 +116,7 @@ export const getCompatibilityAnswers = async (userIds: string[]) => {
from compatibility_answers
where creator_id = any ($1)
`,
- [userIds]
+ [userIds],
)
}
@@ -129,7 +126,7 @@ export async function getAnswersForUser(userId: string) {
const pg = createSupabaseDirectClient()
const answersSelf = await pg.manyOrNone(
'select * from compatibility_answers where creator_id = $1',
- [userId]
+ [userId],
)
return answersSelf
}
diff --git a/backend/shared/src/supabase/init.ts b/backend/shared/src/supabase/init.ts
index f4f90f2e..774204f6 100644
--- a/backend/shared/src/supabase/init.ts
+++ b/backend/shared/src/supabase/init.ts
@@ -1,10 +1,11 @@
-import pgPromise, {IDatabase, ITask} from 'pg-promise'
-import {log, metrics} from '../utils'
-import {IClient, type IConnectionParameters,} from 'pg-promise/typescript/pg-subset'
-import {HOUR_MS} from 'common/util/time'
-import {METRICS_INTERVAL_MS} from 'shared/monitoring/metric-writer'
-import {getMonitoringContext} from 'shared/monitoring/context'
import {ENV_CONFIG} from 'common/envs/constants'
+import {HOUR_MS} from 'common/util/time'
+import pgPromise, {IDatabase, ITask} from 'pg-promise'
+import {IClient, type IConnectionParameters} from 'pg-promise/typescript/pg-subset'
+import {getMonitoringContext} from 'shared/monitoring/context'
+import {METRICS_INTERVAL_MS} from 'shared/monitoring/metric-writer'
+
+import {log, metrics} from '../utils'
export {SupabaseClient} from 'common/supabase/utils'
@@ -33,9 +34,7 @@ pgp.pg.types.setTypeParser(20, (value: any) => parseInt(value, 10))
pgp.pg.types.setTypeParser(1700, parseFloat) // Type Id 1700 = NUMERIC
export type SupabaseTransaction = ITask
-export type SupabaseDirectClient =
- | IDatabase
- | SupabaseTransaction
+export type SupabaseDirectClient = IDatabase | SupabaseTransaction
export function getInstanceId() {
return ENV_CONFIG.supabaseInstanceId
@@ -49,7 +48,7 @@ const newClient = (
props: {
instanceId?: string
password?: string
- } & IConnectionParameters
+ } & IConnectionParameters,
) => {
const {instanceId, password, ...settings} = props
@@ -63,25 +62,25 @@ const newClient = (
const config: any = databaseUrl
? {
- // Use connection string for local/dev Postgres
- connectionString: databaseUrl,
- // Local Postgres typically doesn't need SSL
- ssl: false,
- ...settings,
- }
+ // Use connection string for local/dev Postgres
+ connectionString: databaseUrl,
+ // Local Postgres typically doesn't need SSL
+ ssl: false,
+ ...settings,
+ }
: {
- // Default: connect to Supabase's pooled Postgres
- // This host is IPV4 compatible, for the google cloud VM
- host: 'aws-1-us-west-1.pooler.supabase.com',
- port: 5432,
- user: `postgres.${instanceId}`,
- password: password,
- database: 'postgres',
- pool_mode: 'session',
- ssl: {rejectUnauthorized: false},
- family: 4, // <- forces IPv4
- ...settings,
- }
+ // Default: connect to Supabase's pooled Postgres
+ // This host is IPV4 compatible, for the google cloud VM
+ host: 'aws-1-us-west-1.pooler.supabase.com',
+ port: 5432,
+ user: `postgres.${instanceId}`,
+ password: password,
+ database: 'postgres',
+ pool_mode: 'session',
+ ssl: {rejectUnauthorized: false},
+ family: 4, // <- forces IPv4
+ ...settings,
+ }
// console.debug(config)
@@ -91,24 +90,19 @@ const newClient = (
// Use one connection to avoid WARNING: Creating a duplicate database object for the same connection.
let pgpDirect: IDatabase | null = null
-export function createSupabaseDirectClient(
- instanceId?: string,
- password?: string
-) {
+export function createSupabaseDirectClient(instanceId?: string, password?: string) {
if (pgpDirect) return pgpDirect
const hasDatabaseUrl = !!process.env.DATABASE_URL
// Only enforce Supabase credentials when not using DATABASE_URL
instanceId = instanceId ?? getInstanceId()
if (!hasDatabaseUrl && !instanceId) {
throw new Error(
- "Can't connect to Supabase; no process.env.SUPABASE_INSTANCE_ID and no instance ID in config."
+ "Can't connect to Supabase; no process.env.SUPABASE_INSTANCE_ID and no instance ID in config.",
)
}
password = password ?? getSupabasePwd()
if (!hasDatabaseUrl && !password) {
- throw new Error(
- "Can't connect to Supabase; no process.env.SUPABASE_DB_PASSWORD."
- )
+ throw new Error("Can't connect to Supabase; no process.env.SUPABASE_DB_PASSWORD.")
}
const client = newClient({
instanceId: getInstanceId(),
diff --git a/backend/shared/src/supabase/messages.ts b/backend/shared/src/supabase/messages.ts
index 881bcf2e..add46fd8 100644
--- a/backend/shared/src/supabase/messages.ts
+++ b/backend/shared/src/supabase/messages.ts
@@ -1,6 +1,6 @@
-import {convertSQLtoTS, Row, tsToMillis} from "common/supabase/utils";
-import {ChatMessage, PrivateChatMessage} from "common/chat-message";
-import {decryptMessage} from "shared/encryption";
+import {ChatMessage, PrivateChatMessage} from 'common/chat-message'
+import {convertSQLtoTS, Row, tsToMillis} from 'common/supabase/utils'
+import {decryptMessage} from 'shared/encryption'
export type DbPrivateChatMessage = PrivateChatMessage & {
ciphertext: string
@@ -14,17 +14,16 @@ export const convertChatMessage = (row: Row<'private_user_messages'>) =>
})
export const convertPrivateChatMessage = (row: Row<'private_user_messages'>) => {
- const message = convertSQLtoTS<'private_user_messages', DbPrivateChatMessage>(
- row,
- {created_time: tsToMillis as any,}
- );
- parseMessageObject(message);
+ const message = convertSQLtoTS<'private_user_messages', DbPrivateChatMessage>(row, {
+ created_time: tsToMillis as any,
+ })
+ parseMessageObject(message)
return message
}
type MessageObject = {
- ciphertext: string | null;
- iv: string | null;
+ ciphertext: string | null
+ iv: string | null
tag: string | null
content?: any
}
@@ -35,7 +34,7 @@ export function parseMessageObject(message: MessageObject) {
ciphertext: message.ciphertext,
iv: message.iv,
tag: message.tag,
- });
+ })
message.content = JSON.parse(plaintText)
delete (message as any).ciphertext
delete (message as any).iv
@@ -46,4 +45,4 @@ export function parseMessageObject(message: MessageObject) {
export function getDecryptedMessage(message: MessageObject) {
parseMessageObject(message)
return message.content
-}
\ No newline at end of file
+}
diff --git a/backend/shared/src/supabase/notifications.ts b/backend/shared/src/supabase/notifications.ts
index e6618ed6..7f14df26 100644
--- a/backend/shared/src/supabase/notifications.ts
+++ b/backend/shared/src/supabase/notifications.ts
@@ -1,7 +1,7 @@
import {Notification, NotificationTemplate} from 'common/notifications'
import {SupabaseDirectClient} from 'shared/supabase/init'
-import {broadcast} from 'shared/websockets/server'
import {bulkInsert} from 'shared/supabase/utils'
+import {broadcast} from 'shared/websockets/server'
/**
* Insert a single notification to a single user.
@@ -10,7 +10,7 @@ import {bulkInsert} from 'shared/supabase/utils'
*/
export const insertNotificationToSupabase = async (
notification: Notification,
- pg: SupabaseDirectClient
+ pg: SupabaseDirectClient,
) => {
// Check if this notification has a template_id (new style)
if (notification.templateId) {
@@ -26,7 +26,7 @@ export const insertNotificationToSupabase = async (
isSeen: notification.isSeen,
viewTime: notification.viewTime,
},
- ]
+ ],
)
} else {
// Legacy style - store full notification in data
@@ -34,10 +34,10 @@ export const insertNotificationToSupabase = async (
`insert into user_notifications (user_id, notification_id, data)
values ($1, $2, $3)
on conflict do nothing`,
- [notification.userId, notification.id, notification]
+ [notification.userId, notification.id, notification],
)
}
- broadcast(`user-notifications/${notification.userId}`, { notification })
+ broadcast(`user-notifications/${notification.userId}`, {notification})
}
/**
@@ -45,7 +45,7 @@ export const insertNotificationToSupabase = async (
*/
export const bulkInsertNotifications = async (
notifications: Notification[],
- pg: SupabaseDirectClient
+ pg: SupabaseDirectClient,
) => {
await bulkInsert(
pg,
@@ -54,10 +54,10 @@ export const bulkInsertNotifications = async (
user_id: n.userId,
notification_id: n.id,
data: n,
- }))
+ })),
)
notifications.forEach((notification) =>
- broadcast(`user-notifications/${notification.userId}`, { notification })
+ broadcast(`user-notifications/${notification.userId}`, {notification}),
)
}
@@ -67,7 +67,7 @@ export const bulkInsertNotifications = async (
*/
export const createNotificationTemplate = async (
template: NotificationTemplate,
- pg: SupabaseDirectClient
+ pg: SupabaseDirectClient,
): Promise => {
await pg.none(
`insert into notification_templates
@@ -90,7 +90,7 @@ export const createNotificationTemplate = async (
template.sourceUpdateType ?? null,
template.createdTime,
template.data ?? {},
- ]
+ ],
)
return template.id
}
@@ -132,7 +132,7 @@ export const createUserNotifications = async (
export const createBulkNotification = async (
template: Omit,
userIds: string[],
- pg: SupabaseDirectClient
+ pg: SupabaseDirectClient,
) => {
const timestamp = Date.now()
const templateId = `${template.sourceType}-${timestamp}`
@@ -144,7 +144,7 @@ export const createBulkNotification = async (
id: templateId,
createdTime: timestamp,
},
- pg
+ pg,
)
// Create lightweight user notifications
diff --git a/backend/shared/src/supabase/options.ts b/backend/shared/src/supabase/options.ts
index 378890e3..e0d406e6 100644
--- a/backend/shared/src/supabase/options.ts
+++ b/backend/shared/src/supabase/options.ts
@@ -1,5 +1,5 @@
-import {createSupabaseDirectClient} from 'shared/supabase/init'
import {OPTION_TABLES, OptionTableKey} from 'common/profiles/constants'
+import {createSupabaseDirectClient} from 'shared/supabase/init'
interface CacheEntry {
data: Record>
@@ -14,12 +14,15 @@ export async function getOptionsIdsToLabels(locale: string = 'en') {
const now = Date.now()
const cached = cache.get(cacheKey)
- if (cached && (now - cached.timestamp) < CACHE_TTL) {
+ if (cached && now - cached.timestamp < CACHE_TTL) {
return cached.data
}
// console.log("Fetching getOptionsIdsToLabels...")
const pg = createSupabaseDirectClient()
- const result: Record> = {} as Record>
+ const result: Record> = {} as Record<
+ OptionTableKey,
+ Record
+ >
for (const tableKey of OPTION_TABLES) {
// const rows = await pg.manyOrNone(`
@@ -33,19 +36,22 @@ export async function getOptionsIdsToLabels(locale: string = 'en') {
// LEFT JOIN ${tableKey}_translations ON ${tableKey}.id = ${tableKey}_translations.id
// ORDER BY name ASC
// `, [locale])
- const rows = await pg.manyOrNone(`SELECT id, name
+ const rows = await pg.manyOrNone(
+ `SELECT id, name
FROM ${tableKey}
- ORDER BY name`, [locale])
+ ORDER BY name`,
+ [locale],
+ )
const idToName: Record = {}
- rows.forEach(row => idToName[row.id] = row.name)
+ rows.forEach((row) => (idToName[row.id] = row.name))
result[tableKey] = idToName
}
cache.set(cacheKey, {
data: result,
- timestamp: Date.now()
+ timestamp: Date.now(),
})
// console.log({result})
return result
-}
\ No newline at end of file
+}
diff --git a/backend/shared/src/supabase/sql-builder.ts b/backend/shared/src/supabase/sql-builder.ts
index 0d101ab9..25d79446 100644
--- a/backend/shared/src/supabase/sql-builder.ts
+++ b/backend/shared/src/supabase/sql-builder.ts
@@ -1,6 +1,7 @@
-import { last } from 'lodash'
-import { buildArray } from 'common/util/array'
-import { pgp } from './init'
+import {buildArray} from 'common/util/array'
+import {last} from 'lodash'
+
+import {pgp} from './init'
export type SqlBuilder = {
with: string[]
@@ -49,44 +50,44 @@ export function buildSql(...parts: Args): SqlBuilder {
export function withClause(clause: string, formatValues?: any) {
const formattedWith = pgp.as.format(clause, formatValues)
- return buildSql({ with: formattedWith })
+ return buildSql({with: formattedWith})
}
export function select(clause: string) {
- return buildSql({ select: clause })
+ return buildSql({select: clause})
}
export function from(clause: string, formatValues?: any) {
const from = pgp.as.format(clause, formatValues)
- return buildSql({ from })
+ return buildSql({from})
}
export function join(clause: string, formatValues?: any) {
const join = pgp.as.format(clause, formatValues)
- return buildSql({ join })
+ return buildSql({join})
}
export function leftJoin(clause: string, formatValues?: any) {
const leftJoin = pgp.as.format(clause, formatValues)
- return buildSql({ leftJoin })
+ return buildSql({leftJoin})
}
export function groupBy(clause: string) {
- return buildSql({ groupBy: clause })
+ return buildSql({groupBy: clause})
}
export function where(clause: string, formatValues?: any) {
const where = pgp.as.format(clause, formatValues)
- return buildSql({ where })
+ return buildSql({where})
}
export function orderBy(clause: string, formatValues?: any) {
const orderBy = pgp.as.format(clause, formatValues)
- return buildSql({ orderBy })
+ return buildSql({orderBy})
}
export function limit(limit: number, offset?: number) {
- return buildSql({ limit, offset })
+ return buildSql({limit, offset})
}
export function renderSql(...args: Args) {
@@ -110,11 +111,10 @@ export function renderSql(...args: Args) {
from && `from ${from}`,
join.length && `join ${join.join(' join ')}`,
leftJoin.length && `left join ${leftJoin.join(' left join ')}`,
- where.length &&
- `where ${where.map((clause) => `(${clause})`).join(' and ')}`,
+ where.length && `where ${where.map((clause) => `(${clause})`).join(' and ')}`,
groupBy.length && `group by ${groupBy.join(', ')}`,
orderBy.length && `order by ${orderBy.join(', ')}`,
limit && `limit ${limit}`,
- limit && offset && `offset ${offset}`
+ limit && offset && `offset ${offset}`,
).join('\n')
}
diff --git a/backend/shared/src/supabase/users.ts b/backend/shared/src/supabase/users.ts
index 99b5fd93..0a694e27 100644
--- a/backend/shared/src/supabase/users.ts
+++ b/backend/shared/src/supabase/users.ts
@@ -1,18 +1,12 @@
-import { User } from 'common/user'
-import { SupabaseDirectClient } from 'shared/supabase/init'
-import {
- broadcastUpdatedPrivateUser,
- broadcastUpdatedUser,
-} from 'shared/websockets/helpers'
-import { DataUpdate, updateData } from './utils'
+import {User} from 'common/user'
+import {SupabaseDirectClient} from 'shared/supabase/init'
+import {broadcastUpdatedPrivateUser, broadcastUpdatedUser} from 'shared/websockets/helpers'
+
+import {DataUpdate, updateData} from './utils'
/** only updates data column. do not use for name, username */
-export const updateUser = async (
- db: SupabaseDirectClient,
- id: string,
- update: Partial
-) => {
- const fullUpdate = { id, ...update }
+export const updateUser = async (db: SupabaseDirectClient, id: string, update: Partial) => {
+ const fullUpdate = {id, ...update}
await updateData(db, 'users', 'id', fullUpdate)
broadcastUpdatedUser(fullUpdate)
}
@@ -20,8 +14,8 @@ export const updateUser = async (
export const updatePrivateUser = async (
db: SupabaseDirectClient,
id: string,
- update: DataUpdate<'private_users'>
+ update: DataUpdate<'private_users'>,
) => {
- await updateData(db, 'private_users', 'id', { id, ...update })
+ await updateData(db, 'private_users', 'id', {id, ...update})
broadcastUpdatedPrivateUser(id)
}
diff --git a/backend/shared/src/supabase/utils.ts b/backend/shared/src/supabase/utils.ts
index 6927044e..59152ead 100644
--- a/backend/shared/src/supabase/utils.ts
+++ b/backend/shared/src/supabase/utils.ts
@@ -1,70 +1,64 @@
-import { sortBy } from 'lodash'
-import { pgp, SupabaseDirectClient } from './init'
-import { DataFor, Tables, TableName, Column, Row } from 'common/supabase/utils'
+import {Column, DataFor, Row, TableName, Tables} from 'common/supabase/utils'
+import {sortBy} from 'lodash'
-export async function insert<
- T extends TableName,
- ColumnValues extends Tables[T]['Insert']
->(db: SupabaseDirectClient, table: T, values: ColumnValues) {
+import {pgp, SupabaseDirectClient} from './init'
+
+export async function insert(
+ db: SupabaseDirectClient,
+ table: T,
+ values: ColumnValues,
+) {
const columnNames = Object.keys(values)
- const cs = new pgp.helpers.ColumnSet(columnNames, { table })
+ const cs = new pgp.helpers.ColumnSet(columnNames, {table})
const query = pgp.helpers.insert(values, cs)
// Hack to properly cast values.
const q = query.replace(/::(\w*)'/g, "'::$1")
return await db.one>(q + ` returning *`)
}
-export async function bulkInsert<
- T extends TableName,
- ColumnValues extends Tables[T]['Insert']
->(db: SupabaseDirectClient, table: T, values: ColumnValues[]) {
+export async function bulkInsert(
+ db: SupabaseDirectClient,
+ table: T,
+ values: ColumnValues[],
+) {
if (values.length == 0) {
return []
}
const columnNames = Object.keys(values[0])
- const cs = new pgp.helpers.ColumnSet(columnNames, { table })
+ const cs = new pgp.helpers.ColumnSet(columnNames, {table})
const query = pgp.helpers.insert(values, cs)
// Hack to properly cast values.
const q = query.replace(/::(\w*)'/g, "'::$1")
return await db.many>(q + ` returning *`)
}
-export async function update<
- T extends TableName,
- ColumnValues extends Tables[T]['Update']
->(
+export async function update(
db: SupabaseDirectClient,
table: T,
idField: Column,
- values: ColumnValues
+ values: ColumnValues,
) {
const columnNames = Object.keys(values)
- const cs = new pgp.helpers.ColumnSet(columnNames, { table })
+ const cs = new pgp.helpers.ColumnSet(columnNames, {table})
if (!(idField in values)) {
throw new Error(`missing ${idField} in values for ${columnNames}`)
}
- const clause = pgp.as.format(
- `${idField} = $1`,
- values[idField as keyof ColumnValues]
- )
+ const clause = pgp.as.format(`${idField} = $1`, values[idField as keyof ColumnValues])
const query = pgp.helpers.update(values, cs) + ` WHERE ${clause}`
// Hack to properly cast values.
const q = query.replace(/::(\w*)'/g, "'::$1")
return await db.one>(q + ` returning *`)
}
-export async function bulkUpdate<
- T extends TableName,
- ColumnValues extends Tables[T]['Update']
->(
+export async function bulkUpdate(
db: SupabaseDirectClient,
table: T,
idFields: Column[],
- values: ColumnValues[]
+ values: ColumnValues[],
) {
if (values.length) {
const columnNames = Object.keys(values[0])
- const cs = new pgp.helpers.ColumnSet(columnNames, { table })
+ const cs = new pgp.helpers.ColumnSet(columnNames, {table})
const clause = idFields.map((f) => `v.${f} = t.${f}`).join(' and ')
const query = pgp.helpers.update(values, cs) + ` WHERE ${clause}`
// Hack to properly cast values.
@@ -76,24 +70,24 @@ export async function bulkUpdate<
export async function bulkUpsert<
T extends TableName,
ColumnValues extends Tables[T]['Insert'],
- Col extends Column
+ Col extends Column,
>(
db: SupabaseDirectClient,
table: T,
idField: Col | Col[],
values: ColumnValues[],
- onConflict?: string
+ onConflict?: string,
) {
if (!values.length) return
const columnNames = Object.keys(values[0])
- const cs = new pgp.helpers.ColumnSet(columnNames, { table })
+ const cs = new pgp.helpers.ColumnSet(columnNames, {table})
const baseQuery = pgp.helpers.insert(values, cs)
// Hack to properly cast values.
const baseQueryReplaced = baseQuery.replace(/::(\w*)'/g, "'::$1")
const primaryKey = Array.isArray(idField) ? idField.join(', ') : idField
- const upsertAssigns = cs.assignColumns({ from: 'excluded', skip: idField })
+ const upsertAssigns = cs.assignColumns({from: 'excluded', skip: idField})
const query =
`${baseQueryReplaced} on ` +
(onConflict ? onConflict : `conflict(${primaryKey})`) +
@@ -108,21 +102,18 @@ export async function bulkUpdateData(
db: SupabaseDirectClient,
table: T,
// TODO: explicit id field
- updates: (Partial> & { id: string })[]
+ updates: (Partial> & {id: string})[],
) {
if (updates.length > 0) {
const values = updates
- .map(
- ({ id, ...update }) =>
- `('${id}', '${JSON.stringify(update).replace("'", "''")}'::jsonb)`
- )
+ .map(({id, ...update}) => `('${id}', '${JSON.stringify(update).replace("'", "''")}'::jsonb)`)
.join(',\n')
await db.none(
`update ${table} as c
set data = data || v.update
from (values ${values}) as v(id, update)
- where c.id = v.id`
+ where c.id = v.id`,
)
}
}
@@ -132,9 +123,9 @@ export async function updateData(
db: SupabaseDirectClient,
table: T,
idField: Column,
- data: DataUpdate
+ data: DataUpdate,
) {
- const { [idField]: id, ...rest } = data
+ const {[idField]: id, ...rest} = data
if (!id) throw new Error(`Missing id field ${idField} in data`)
const basic: Partial> = {}
@@ -147,16 +138,14 @@ export async function updateData(
basic[key as keyof typeof rest] = val
}
}
- const sortedExtraOperations = sortBy(extras, (statement) =>
- statement.startsWith('-') ? -1 : 1
- )
+ const sortedExtraOperations = sortBy(extras, (statement) => (statement.startsWith('-') ? -1 : 1))
return await db.one>(
`update ${table} set data = data
${sortedExtraOperations.join('\n')}
|| $1
where ${idField} = '${id}' returning *`,
- [JSON.stringify(basic)]
+ [JSON.stringify(basic)],
)
}
@@ -175,7 +164,7 @@ export const FieldVal = {
(fieldName: string) => {
return pgp.as.format(
`|| jsonb_build_object($1, coalesce(data->$1, '[]'::jsonb) || $2:json)`,
- [fieldName, values]
+ [fieldName, values],
)
},
@@ -184,7 +173,7 @@ export const FieldVal = {
(fieldName: string) => {
return pgp.as.format(
`|| jsonb_build_object($1, coalesce(data->$1,'[]'::jsonb) - '{$2:raw}'::text[])`,
- [fieldName, values.join(',')]
+ [fieldName, values.join(',')],
)
},
}
diff --git a/backend/shared/src/utils.ts b/backend/shared/src/utils.ts
index a18198a0..7bac19ed 100644
--- a/backend/shared/src/utils.ts
+++ b/backend/shared/src/utils.ts
@@ -1,14 +1,14 @@
-import {createSupabaseDirectClient, SupabaseDirectClient,} from 'shared/supabase/init'
import {convertPrivateUser, convertUser} from 'common/supabase/users'
import {log, type Logger} from 'shared/monitoring/log'
import {metrics} from 'shared/monitoring/metrics'
+import {createSupabaseDirectClient, SupabaseDirectClient} from 'shared/supabase/init'
export {metrics}
export {log, type Logger}
export const getUser = async (
userId: string,
- pg: SupabaseDirectClient = createSupabaseDirectClient()
+ pg: SupabaseDirectClient = createSupabaseDirectClient(),
) => {
return await pg.oneOrNone(
`select *
@@ -16,13 +16,13 @@ export const getUser = async (
where id = $1
limit 1`,
[userId],
- convertUser
+ convertUser,
)
}
export const getPrivateUser = async (
userId: string,
- pg: SupabaseDirectClient = createSupabaseDirectClient()
+ pg: SupabaseDirectClient = createSupabaseDirectClient(),
) => {
return await pg.oneOrNone(
`select *
@@ -30,19 +30,19 @@ export const getPrivateUser = async (
where id = $1
limit 1`,
[userId],
- convertPrivateUser
+ convertPrivateUser,
)
}
export const getUserByUsername = async (
username: string,
- pg: SupabaseDirectClient = createSupabaseDirectClient()
+ pg: SupabaseDirectClient = createSupabaseDirectClient(),
) => {
const res = await pg.oneOrNone(
`select *
from users
where username ilike $1`,
- username
+ username,
)
return res ? convertUser(res) : null
@@ -50,7 +50,7 @@ export const getUserByUsername = async (
export const getPrivateUserByKey = async (
apiKey: string,
- pg: SupabaseDirectClient = createSupabaseDirectClient()
+ pg: SupabaseDirectClient = createSupabaseDirectClient(),
) => {
return await pg.oneOrNone(
`select *
@@ -58,6 +58,6 @@ export const getPrivateUserByKey = async (
where data ->> 'apiKey' = $1
limit 1`,
[apiKey],
- convertPrivateUser
+ convertPrivateUser,
)
}
diff --git a/backend/shared/src/websockets/helpers.ts b/backend/shared/src/websockets/helpers.ts
index e4e51764..6539ac25 100644
--- a/backend/shared/src/websockets/helpers.ts
+++ b/backend/shared/src/websockets/helpers.ts
@@ -1,16 +1,17 @@
-import { broadcast } from './server'
-import { type User } from 'common/user'
-import { type Comment } from 'common/comment'
+import {type Comment} from 'common/comment'
+import {type User} from 'common/user'
+
+import {broadcast} from './server'
export function broadcastUpdatedPrivateUser(userId: string) {
// don't send private user info because it's private and anyone can listen
broadcast(`private-user/${userId}`, {})
}
-export function broadcastUpdatedUser(user: Partial & { id: string }) {
- broadcast(`user/${user.id}`, { user })
+export function broadcastUpdatedUser(user: Partial & {id: string}) {
+ broadcast(`user/${user.id}`, {user})
}
export function broadcastUpdatedComment(comment: Comment) {
- broadcast(`user/${comment.onUserId}/comment`, { comment })
+ broadcast(`user/${comment.onUserId}/comment`, {comment})
}
diff --git a/backend/shared/src/websockets/server.ts b/backend/shared/src/websockets/server.ts
index d29bb12e..6c19cef7 100644
--- a/backend/shared/src/websockets/server.ts
+++ b/backend/shared/src/websockets/server.ts
@@ -1,16 +1,18 @@
-import { Server as HttpServer } from 'node:http'
-import { Server as WebSocketServer, RawData, WebSocket } from 'ws'
-import { isError } from 'lodash'
-import { log, metrics } from 'shared/utils'
-import { Switchboard } from './switchboard'
+import {Server as HttpServer} from 'node:http'
+
+import {getWebsocketUrl} from 'common/api/utils'
import {
BroadcastPayload,
+ CLIENT_MESSAGE_SCHEMA,
ClientMessage,
ServerMessage,
- CLIENT_MESSAGE_SCHEMA,
} from 'common/api/websockets'
-import {IS_LOCAL} from "common/hosting/constants";
-import {getWebsocketUrl} from "common/api/utils";
+import {IS_LOCAL} from 'common/hosting/constants'
+import {isError} from 'lodash'
+import {log, metrics} from 'shared/utils'
+import {RawData, Server as WebSocketServer, WebSocket} from 'ws'
+
+import {Switchboard} from './switchboard'
// Extend the type definition locally
interface HeartbeatWebSocket extends WebSocket {
@@ -60,7 +62,7 @@ function parseMessage(data: RawData): ClientMessage {
function processMessage(ws: HeartbeatWebSocket, data: RawData): ServerMessage<'ack'> {
try {
const msg = parseMessage(data)
- const { type, txid } = msg
+ const {type, txid} = msg
try {
switch (type) {
case 'identify': {
@@ -84,12 +86,12 @@ function processMessage(ws: HeartbeatWebSocket, data: RawData): ServerMessage<'a
}
} catch (err) {
log.error(err)
- return { type: 'ack', txid, success: false, error: serializeError(err) }
+ return {type: 'ack', txid, success: false, error: serializeError(err)}
}
- return { type: 'ack', txid, success: true }
+ return {type: 'ack', txid, success: true}
} catch (err) {
log.error(err)
- return { type: 'ack', success: false, error: serializeError(err) }
+ return {type: 'ack', success: false, error: serializeError(err)}
}
}
@@ -103,25 +105,25 @@ export function broadcastMulti(topics: string[], data: BroadcastPayload) {
([ws, _]) =>
new Promise((resolve) =>
ws.send(json, (err) => {
- if (err) log.error('Broadcast error', { error: err })
+ if (err) log.error('Broadcast error', {error: err})
resolve()
- })
- )
- )
- ).catch((err) => log.error('Broadcast failed', { error: err }))
+ }),
+ ),
+ ),
+ ).catch((err) => log.error('Broadcast failed', {error: err}))
}
// it isn't secure to do this in prod for auth reasons (maybe?)
// but it's super convenient for testing
if (IS_LOCAL) {
- const msg = { type: 'broadcast', topic: '*', topics, data }
+ const msg = {type: 'broadcast', topic: '*', topics, data}
sendToSubscribers('*', msg)
}
for (const topic of topics) {
- const msg = { type: 'broadcast', topic, data }
+ const msg = {type: 'broadcast', topic, data}
sendToSubscribers(topic, msg)
- metrics.inc('ws/broadcasts_sent', { topic })
+ metrics.inc('ws/broadcasts_sent', {topic})
}
}
@@ -130,30 +132,30 @@ export function broadcast(topic: string, data: BroadcastPayload) {
}
export function listen(server: HttpServer, path: string) {
- const wss = new WebSocketServer({ server, path })
+ const wss = new WebSocketServer({server, path})
let deadConnectionCleaner: NodeJS.Timeout | undefined
wss.on('listening', () => {
log.info(`Web socket server listening on ${path}. ${getWebsocketUrl()}`)
deadConnectionCleaner = setInterval(() => {
for (const ws of wss.clients as Set) {
if (ws.isAlive === false) {
- log.debug('Terminating dead connection');
- ws.terminate();
- continue;
+ log.debug('Terminating dead connection')
+ ws.terminate()
+ continue
}
- ws.isAlive = false;
+ ws.isAlive = false
// log.debug('Sending ping to client');
- ws.ping();
+ ws.ping()
}
- }, 25000);
+ }, 25000)
})
wss.on('error', (err) => {
- log.error('Error on websocket server.', { error: err })
+ log.error('Error on websocket server.', {error: err})
})
wss.on('connection', (ws: HeartbeatWebSocket) => {
- ws.isAlive = true;
+ ws.isAlive = true
// log.debug('Received pong from client');
- ws.on('pong', () => (ws.isAlive = true));
+ ws.on('pong', () => (ws.isAlive = true))
metrics.inc('ws/connections_established')
metrics.set('ws/open_connections', wss.clients.size)
log.debug('WS client connected.')
@@ -166,11 +168,11 @@ export function listen(server: HttpServer, path: string) {
ws.on('close', (code, reason) => {
metrics.inc('ws/connections_terminated')
metrics.set('ws/open_connections', wss.clients.size)
- log.debug(`WS client disconnected.`, { code, reason: reason.toString() })
+ log.debug(`WS client disconnected.`, {code, reason: reason.toString()})
SWITCHBOARD.disconnect(ws)
})
ws.on('error', (err) => {
- log.error('Error on websocket connection.', { error: err })
+ log.error('Error on websocket connection.', {error: err})
})
})
wss.on('close', function close() {
diff --git a/backend/shared/src/websockets/switchboard.ts b/backend/shared/src/websockets/switchboard.ts
index 58f3d8ba..ce3b2056 100644
--- a/backend/shared/src/websockets/switchboard.ts
+++ b/backend/shared/src/websockets/switchboard.ts
@@ -1,4 +1,4 @@
-import { WebSocket } from 'ws'
+import {WebSocket} from 'ws'
export type ClientState = {
uid?: string
@@ -31,7 +31,7 @@ export class Switchboard {
if (existing != null) {
throw new Error("Client already connected! Shouldn't happen.")
}
- this.clients.set(ws, { lastSeen: Date.now(), subscriptions: new Set() })
+ this.clients.set(ws, {lastSeen: Date.now(), subscriptions: new Set()})
}
disconnect(ws: WebSocket) {
this.getClient(ws)
diff --git a/backend/shared/tests/integration/README.md b/backend/shared/tests/integration/README.md
index ad66a436..fcebf590 100644
--- a/backend/shared/tests/integration/README.md
+++ b/backend/shared/tests/integration/README.md
@@ -1 +1 @@
-Note: may not be needed. `shared` rarely needs integration tests.
\ No newline at end of file
+Note: may not be needed. `shared` rarely needs integration tests.
diff --git a/backend/shared/tests/unit/compute-score.unit.test.ts b/backend/shared/tests/unit/compute-score.unit.test.ts
index 7d88af71..46105183 100644
--- a/backend/shared/tests/unit/compute-score.unit.test.ts
+++ b/backend/shared/tests/unit/compute-score.unit.test.ts
@@ -1,140 +1,143 @@
-import {recomputeCompatibilityScoresForUser} from "shared/compatibility/compute-scores";
-import * as supabaseInit from "shared/supabase/init";
-import * as profilesSupabaseModules from "shared/profiles/supabase";
-import * as compatibilityScoreModules from "common/profiles/compatibility-score";
-import {Profile} from "common/profiles/profile";
+import * as compatibilityScoreModules from 'common/profiles/compatibility-score'
+import {Profile} from 'common/profiles/profile'
+import {recomputeCompatibilityScoresForUser} from 'shared/compatibility/compute-scores'
+import * as profilesSupabaseModules from 'shared/profiles/supabase'
+import * as supabaseInit from 'shared/supabase/init'
jest.mock('shared/profiles/supabase')
jest.mock('shared/supabase/init')
jest.mock('common/profiles/compatibility-score')
-
describe('recomputeCompatibilityScoresForUser', () => {
beforeEach(() => {
- jest.resetAllMocks();
+ jest.resetAllMocks()
const mockPg = {
none: jest.fn().mockResolvedValue(null),
one: jest.fn().mockResolvedValue(null),
oneOrNone: jest.fn().mockResolvedValue(null),
any: jest.fn().mockResolvedValue([]),
- } as any;
- (supabaseInit.createSupabaseDirectClient as jest.Mock)
- .mockReturnValue(mockPg);
- });
+ } as any
+ ;(supabaseInit.createSupabaseDirectClient as jest.Mock).mockReturnValue(mockPg)
+ })
afterEach(() => {
- jest.restoreAllMocks();
- });
+ jest.restoreAllMocks()
+ })
describe('should', () => {
it('successfully get compute score when supplied with a valid user Id', async () => {
- const mockUser = {userId: "123"};
+ const mockUser = {userId: '123'}
const mockUserProfile = {
id: 1,
user_id: '123',
user: {
- username: "Mockuser.getProfile"
+ username: 'Mockuser.getProfile',
},
- created_time: "10:30",
- explanation: "mockExplanation3",
+ created_time: '10:30',
+ explanation: 'mockExplanation3',
importance: 3,
- };
+ }
const mockGenderCompatibleProfiles = [
{
age: 20,
- user_id: "1",
+ user_id: '1',
company: 'Mock Texan Roadhouse',
drinks_per_month: 3,
- city: 'Mockingdale'
+ city: 'Mockingdale',
},
{
age: 23,
- user_id: "2",
+ user_id: '2',
company: 'Chicken fried goose',
drinks_per_month: 2,
- city: 'Mockingdale'
+ city: 'Mockingdale',
},
{
age: 40,
- user_id: "3",
+ user_id: '3',
company: 'World Peace',
drinks_per_month: 10,
- city: 'Velvet Suite'
+ city: 'Velvet Suite',
},
- ] as Partial [];
+ ] as Partial[]
const mockProfileCompatibilityAnswers = [
{
- created_time: "10:30",
- creator_id: "3",
- explanation: "mockExplanation3",
+ created_time: '10:30',
+ creator_id: '3',
+ explanation: 'mockExplanation3',
id: 3,
- importance: 3
+ importance: 3,
},
{
- created_time: "10:20",
- creator_id: "2",
- explanation: "mockExplanation2",
+ created_time: '10:20',
+ creator_id: '2',
+ explanation: 'mockExplanation2',
id: 2,
- importance: 2
+ importance: 2,
},
{
- created_time: "10:10",
- creator_id: "1",
- explanation: "mockExplanation",
+ created_time: '10:10',
+ creator_id: '1',
+ explanation: 'mockExplanation',
id: 1,
- importance: 1
+ importance: 1,
},
- ];
+ ]
const mockCompatibilityScore = {
score: 4,
- confidence: "low"
- };
- const mockAnswersForUser = [{
- created_time: "",
- creator_id: mockUser.userId,
- explanation: "",
- id: 1,
- importance: 1,
- multiple_choice: 0,
- pref_choices: [0, 1],
- question_id: 1,
- }];
+ confidence: 'low',
+ }
+ const mockAnswersForUser = [
+ {
+ created_time: '',
+ creator_id: mockUser.userId,
+ explanation: '',
+ id: 1,
+ importance: 1,
+ multiple_choice: 0,
+ pref_choices: [0, 1],
+ question_id: 1,
+ },
+ ]
- (profilesSupabaseModules.getProfile as jest.Mock)
- .mockResolvedValue(mockUserProfile);
- (profilesSupabaseModules.getGenderCompatibleProfiles as jest.Mock)
- .mockResolvedValue(mockGenderCompatibleProfiles);
- (profilesSupabaseModules.getCompatibilityAnswers as jest.Mock)
- .mockResolvedValue(mockProfileCompatibilityAnswers);
- (profilesSupabaseModules.getAnswersForUser as jest.Mock)
- .mockResolvedValue(mockAnswersForUser);
- (compatibilityScoreModules.getCompatibilityScore as jest.Mock)
- .mockReturnValue(mockCompatibilityScore);
- (compatibilityScoreModules.hasAnsweredQuestions as jest.Mock)
- .mockReturnValue(true);
+ ;(profilesSupabaseModules.getProfile as jest.Mock).mockResolvedValue(mockUserProfile)
+ ;(profilesSupabaseModules.getGenderCompatibleProfiles as jest.Mock).mockResolvedValue(
+ mockGenderCompatibleProfiles,
+ )
+ ;(profilesSupabaseModules.getCompatibilityAnswers as jest.Mock).mockResolvedValue(
+ mockProfileCompatibilityAnswers,
+ )
+ ;(profilesSupabaseModules.getAnswersForUser as jest.Mock).mockResolvedValue(
+ mockAnswersForUser,
+ )
+ ;(compatibilityScoreModules.getCompatibilityScore as jest.Mock).mockReturnValue(
+ mockCompatibilityScore,
+ )
+ ;(compatibilityScoreModules.hasAnsweredQuestions as jest.Mock).mockReturnValue(true)
- const results = await recomputeCompatibilityScoresForUser(mockUser.userId);
- expect(profilesSupabaseModules.getProfile).toBeCalledWith(mockUser.userId);
- expect(profilesSupabaseModules.getProfile).toBeCalledTimes(1);
- expect(profilesSupabaseModules.getGenderCompatibleProfiles).toBeCalledWith(mockUserProfile);
- expect(profilesSupabaseModules.getGenderCompatibleProfiles).toBeCalledTimes(1);
- expect(compatibilityScoreModules.getCompatibilityScore).toBeCalledTimes(mockGenderCompatibleProfiles.length)
+ const results = await recomputeCompatibilityScoresForUser(mockUser.userId)
+ expect(profilesSupabaseModules.getProfile).toBeCalledWith(mockUser.userId)
+ expect(profilesSupabaseModules.getProfile).toBeCalledTimes(1)
+ expect(profilesSupabaseModules.getGenderCompatibleProfiles).toBeCalledWith(mockUserProfile)
+ expect(profilesSupabaseModules.getGenderCompatibleProfiles).toBeCalledTimes(1)
+ expect(compatibilityScoreModules.getCompatibilityScore).toBeCalledTimes(
+ mockGenderCompatibleProfiles.length,
+ )
// expect(results.profile).toEqual(mockUserProfile);
// expect(results.compatibleProfiles).toContain(mockGenderCompatibleProfiles[0]);
- expect(results?.[0][0]).toEqual("1");
- expect(results?.[0][1]).toEqual("123");
- expect(results?.[0][2]).toBeCloseTo(mockCompatibilityScore.score, 2);
- });
+ expect(results?.[0][0]).toEqual('1')
+ expect(results?.[0][1]).toEqual('123')
+ expect(results?.[0][2]).toBeCloseTo(mockCompatibilityScore.score, 2)
+ })
it('throw an error if there is no profile matching the user Id', async () => {
- const mockUser = {userId: "123"};
+ const mockUser = {userId: '123'}
- expect(recomputeCompatibilityScoresForUser(mockUser.userId))
- .rejects
- .toThrowError('Profile not found');
- expect(profilesSupabaseModules.getProfile).toBeCalledWith(mockUser.userId);
- expect(profilesSupabaseModules.getProfile).toBeCalledTimes(1);
- });
-
- });
-});
+ expect(recomputeCompatibilityScoresForUser(mockUser.userId)).rejects.toThrowError(
+ 'Profile not found',
+ )
+ expect(profilesSupabaseModules.getProfile).toBeCalledWith(mockUser.userId)
+ expect(profilesSupabaseModules.getProfile).toBeCalledTimes(1)
+ })
+ })
+})
diff --git a/backend/shared/tsconfig.json b/backend/shared/tsconfig.json
index f2fe40b9..7bd3e6c3 100644
--- a/backend/shared/tsconfig.json
+++ b/backend/shared/tsconfig.json
@@ -1,12 +1,9 @@
{
"compilerOptions": {
- "rootDir": "src",
- "composite": true,
"module": "commonjs",
"moduleResolution": "node",
"noImplicitReturns": true,
"outDir": "lib",
- "tsBuildInfoFile": "lib/tsconfig.tsbuildinfo",
"sourceMap": true,
"strict": true,
"target": "esnext",
@@ -15,23 +12,13 @@
"lib": ["esnext"],
"skipLibCheck": true,
"paths": {
- "common/*": ["../../common/src/*",
- "../../../common/lib/*"
- ],
- "shared/*": [
- "./src/*"
- ],
+ "common/*": ["../../common/src/*", "../../../common/lib/*"],
+ "shared/*": ["./src/*"]
}
},
"ts-node": {
- "require": [
- "tsconfig-paths/register"
- ]
+ "require": ["tsconfig-paths/register"]
},
- "references": [
- {
- "path": "../../common"
- },
- ],
- "include": ["src/**/*.ts", "src/**/*.tsx"]
+ "include": ["src/**/*.ts", "src/**/*.tsx"],
+ "exclude": ["**/*.test.ts", "**/*.spec.ts"]
}
diff --git a/backend/shared/tsconfig.test.json b/backend/shared/tsconfig.test.json
index 55c25e92..4515326e 100644
--- a/backend/shared/tsconfig.test.json
+++ b/backend/shared/tsconfig.test.json
@@ -13,5 +13,6 @@
"email/*": ["../email/emails/*"]
}
},
- "include": ["tests/**/*.ts", "src/**/*.ts"]
+ "include": ["tests/**/*.ts", "src/**/*.ts"],
+ "exclude": []
}
diff --git a/common/.eslintrc.js b/common/.eslintrc.js
index 03e87e6c..c4692151 100644
--- a/common/.eslintrc.js
+++ b/common/.eslintrc.js
@@ -1,58 +1,57 @@
module.exports = {
- plugins: ['lodash', 'unused-imports'],
- extends: ['eslint:recommended'],
- ignorePatterns: ['lib', 'coverage', 'jest.config.js'],
- env: {
- node: true,
- },
- overrides: [
- {
- files: ['**/*.ts'],
- plugins: ['@typescript-eslint'],
- extends: ['plugin:@typescript-eslint/recommended', 'prettier'],
- parser: '@typescript-eslint/parser',
- parserOptions: {
- tsconfigRootDir: __dirname,
- project: ['./tsconfig.json'],
- },
- rules: {
- "@typescript-eslint/no-empty-object-type": "error", // replaces banning {}
- "@typescript-eslint/no-unsafe-function-type": "error", // replaces banning Function
- "@typescript-eslint/no-wrapper-object-types": "error", // replaces banning String, Number, etc.
- '@typescript-eslint/no-explicit-any': 'off',
- '@typescript-eslint/no-extra-semi': 'off',
- '@typescript-eslint/no-unused-vars': [
- 'warn',
- {
- argsIgnorePattern: '^_',
- varsIgnorePattern: '^_',
- caughtErrorsIgnorePattern: '^_',
- },
- ],
- 'unused-imports/no-unused-imports': 'warn',
- 'no-constant-condition': 'off',
- },
- },
- {
- "files": ["**/*.test.ts", "**/*.test.tsx", "**/tests/**/*.ts"],
- "env": {
- "jest": true // makes expect, describe, it globals known
- },
- "rules": {
- "@typescript-eslint/no-explicit-any": "off",
- "@typescript-eslint/no-non-null-assertion": "off"
- },
- parserOptions: {
- tsconfigRootDir: __dirname,
- project: ['./tsconfig.test.json'],
- },
- }
- ],
- rules: {
- 'linebreak-style': [
- 'error',
- process.platform === 'win32' ? 'windows' : 'unix',
+ plugins: ['lodash', 'unused-imports', 'simple-import-sort'],
+ extends: ['eslint:recommended'],
+ ignorePatterns: ['lib', 'coverage', 'jest.config.js'],
+ env: {
+ node: true,
+ },
+ overrides: [
+ {
+ files: ['**/*.ts'],
+ plugins: ['@typescript-eslint'],
+ extends: ['plugin:@typescript-eslint/recommended', 'prettier'],
+ parser: '@typescript-eslint/parser',
+ parserOptions: {
+ tsconfigRootDir: __dirname,
+ project: ['./tsconfig.json'],
+ },
+ rules: {
+ '@typescript-eslint/no-empty-object-type': 'error', // replaces banning {}
+ '@typescript-eslint/no-unsafe-function-type': 'error', // replaces banning Function
+ '@typescript-eslint/no-wrapper-object-types': 'error', // replaces banning String, Number, etc.
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-extra-semi': 'off',
+ '@typescript-eslint/no-unused-vars': [
+ 'warn',
+ {
+ argsIgnorePattern: '^_',
+ varsIgnorePattern: '^_',
+ caughtErrorsIgnorePattern: '^_',
+ },
],
- 'lodash/import-scope': [2, 'member'],
+ 'unused-imports/no-unused-imports': 'warn',
+ 'no-constant-condition': 'off',
+ },
},
+ {
+ files: ['**/*.test.ts', '**/*.test.tsx', '**/tests/**/*.ts'],
+ env: {
+ jest: true, // makes expect, describe, it globals known
+ },
+ rules: {
+ '@typescript-eslint/no-explicit-any': 'off',
+ '@typescript-eslint/no-non-null-assertion': 'off',
+ },
+ parserOptions: {
+ tsconfigRootDir: __dirname,
+ project: ['./tsconfig.test.json'],
+ },
+ },
+ ],
+ rules: {
+ 'linebreak-style': ['error', process.platform === 'win32' ? 'windows' : 'unix'],
+ 'lodash/import-scope': [2, 'member'],
+ 'simple-import-sort/imports': 'error',
+ 'simple-import-sort/exports': 'error',
+ },
}
diff --git a/common/.prettierignore b/common/.prettierignore
new file mode 100644
index 00000000..d4cf806b
--- /dev/null
+++ b/common/.prettierignore
@@ -0,0 +1,34 @@
+# Dependencies
+node_modules
+.yarn
+
+# Build outputs
+dist
+build
+.next
+out
+lib
+
+# Generated files
+coverage
+*.min.js
+*.min.css
+
+# Database / migrations
+**/*.sql
+
+# Config / lock files
+yarn.lock
+package-lock.json
+pnpm-lock.yaml
+
+# Android / iOS
+android
+ios
+capacitor.config.ts
+
+# Playwright
+tests/reports
+playwright-report
+
+coverage
\ No newline at end of file
diff --git a/common/jest.config.js b/common/jest.config.js
index c6a57df2..a79fa65d 100644
--- a/common/jest.config.js
+++ b/common/jest.config.js
@@ -3,14 +3,11 @@ const {pathsToModuleNameMapper} = require('ts-jest')
const {compilerOptions} = require('./tsconfig')
module.exports = {
- preset: 'ts-jest',
- testEnvironment: 'node',
- moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
- prefix: '/',
- }),
- testMatch: ['**/*.test.ts'],
- collectCoverageFrom: [
- "src/**/*.{ts,tsx}",
- "!src/**/*.d.ts"
- ],
+ preset: 'ts-jest',
+ testEnvironment: 'node',
+ moduleNameMapper: pathsToModuleNameMapper(compilerOptions.paths, {
+ prefix: '/',
+ }),
+ testMatch: ['**/*.test.ts'],
+ collectCoverageFrom: ['src/**/*.{ts,tsx}', '!src/**/*.d.ts'],
}
diff --git a/common/package.json b/common/package.json
index 07a7012f..4077f375 100644
--- a/common/package.json
+++ b/common/package.json
@@ -5,10 +5,9 @@
"scripts": {
"build": "tsc -b && tsc-alias",
"compile": "tsc -b",
- "verify": "yarn --cwd=.. verify",
"lint": "npx eslint . --max-warnings 0",
"lint-fix": "npx eslint . --fix",
- "verify:dir": "npx eslint . --max-warnings 0",
+ "typecheck": "npx tsc --noEmit",
"test": "jest --config jest.config.js --passWithNoTests"
},
"sideEffects": false,
@@ -32,6 +31,6 @@
"@types/string-similarity": "4.0.0",
"jest": "29.3.1",
"supabase": "2.15.8",
- "ts-jest": "29.0.3"
+ "ts-jest": "29.4.6"
}
}
diff --git a/common/src/api/schema.ts b/common/src/api/schema.ts
index f8877273..3152ab5b 100644
--- a/common/src/api/schema.ts
+++ b/common/src/api/schema.ts
@@ -1,22 +1,28 @@
-import {arraybeSchema, baseProfilesSchema, combinedProfileSchema, contentSchema, zBoolean,} from 'common/api/zod-types'
+import {
+ arraybeSchema,
+ baseProfilesSchema,
+ combinedProfileSchema,
+ contentSchema,
+ zBoolean,
+} from 'common/api/zod-types'
import {PrivateChatMessage} from 'common/chat-message'
+import {Notification} from 'common/notifications'
import {CompatibilityScore} from 'common/profiles/compatibility-score'
-import {MAX_COMPATIBILITY_QUESTION_LENGTH, OPTION_TABLES,} from 'common/profiles/constants'
+import {MAX_COMPATIBILITY_QUESTION_LENGTH, OPTION_TABLES} from 'common/profiles/constants'
import {Profile, ProfileRow} from 'common/profiles/profile'
+import {PrivateMessageChannel} from 'common/supabase/private-messages'
import {Row} from 'common/supabase/utils'
import {PrivateUser, User} from 'common/user'
+import {notification_preference} from 'common/user-notification-preferences'
+import {arrify} from 'common/util/array'
import {z} from 'zod'
+
import {LikeData, ShipData} from './profile-types'
import {FullUser} from './user-types'
-import {PrivateMessageChannel} from 'common/supabase/private-messages'
-import {Notification} from 'common/notifications'
-import {arrify} from 'common/util/array'
-import {notification_preference} from 'common/user-notification-preferences'
// mqp: very unscientific, just balancing our willingness to accept load
// with user willingness to put up with stale data
-export const DEFAULT_CACHE_STRATEGY =
- 'public, max-age=5, stale-while-revalidate=10'
+export const DEFAULT_CACHE_STRATEGY = 'public, max-age=5, stale-while-revalidate=10'
type APIGenericSchema = {
// GET is for retrieval, POST is to mutate something, PUT is idempotent mutation (can be repeated safely)
@@ -37,7 +43,7 @@ type APIGenericSchema = {
tag?: string
}
-let _apiTypeCheck: { [x: string]: APIGenericSchema }
+let _apiTypeCheck: {[x: string]: APIGenericSchema}
export const API = (_apiTypeCheck = {
health: {
@@ -64,7 +70,7 @@ export const API = (_apiTypeCheck = {
authed: true,
rateLimited: false,
props: z.object({}),
- returns: {} as { jwt: string },
+ returns: {} as {jwt: string},
summary: 'Return a Supabase JWT for authenticated clients',
tag: 'Tokens',
},
@@ -146,7 +152,7 @@ export const API = (_apiTypeCheck = {
method: 'POST',
authed: true,
rateLimited: true,
- returns: {} as { user: User; privateUser: PrivateUser },
+ returns: {} as {user: User; privateUser: PrivateUser},
props: z
.object({
deviceToken: z.string().optional(),
@@ -624,7 +630,7 @@ export const API = (_apiTypeCheck = {
}),
returns: {
channels: [] as PrivateMessageChannel[],
- memberIdsByChannelId: {} as { [channelId: string]: string[] },
+ memberIdsByChannelId: {} as {[channelId: string]: string[]},
},
summary: 'List private message channel memberships',
tag: 'Messages',
@@ -647,10 +653,7 @@ export const API = (_apiTypeCheck = {
authed: true,
rateLimited: false,
props: z.object({
- channelIds: z
- .array(z.coerce.number())
- .or(z.coerce.number())
- .transform(arrify),
+ channelIds: z.array(z.coerce.number()).or(z.coerce.number()).transform(arrify),
}),
returns: [] as [number, string][],
summary: 'Get last seen times for one or more channels',
@@ -850,7 +853,7 @@ export const API = (_apiTypeCheck = {
authed: false,
rateLimited: false,
props: z.object({}),
- returns: {} as { count: number },
+ returns: {} as {count: number},
summary: 'Get the total number of messages (public endpoint)',
tag: 'Messages',
},
@@ -904,7 +907,7 @@ export const API = (_apiTypeCheck = {
method: 'POST',
authed: true,
rateLimited: true,
- returns: {} as { success: boolean },
+ returns: {} as {success: boolean},
props: z.object({
eventId: z.string(),
}),
@@ -915,7 +918,7 @@ export const API = (_apiTypeCheck = {
method: 'POST',
authed: true,
rateLimited: true,
- returns: {} as { success: boolean },
+ returns: {} as {success: boolean},
props: z.object({
eventId: z.string(),
status: z.enum(['going', 'maybe', 'not_going']),
@@ -927,7 +930,7 @@ export const API = (_apiTypeCheck = {
method: 'POST',
authed: true,
rateLimited: true,
- returns: {} as { success: boolean },
+ returns: {} as {success: boolean},
props: z.object({
eventId: z.string(),
}),
@@ -968,7 +971,7 @@ export const API = (_apiTypeCheck = {
method: 'POST',
authed: true,
rateLimited: false,
- returns: {} as { success: boolean },
+ returns: {} as {success: boolean},
props: z
.object({
eventId: z.string(),
@@ -991,17 +994,15 @@ export type APIPath = keyof typeof API
export type APISchema = (typeof API)[N]
export type APIParams = z.input['props']>
-export type ValidatedAPIParams = z.output<
- APISchema['props']
->
+export type ValidatedAPIParams = z.output['props']>
export type APIResponse =
APISchema extends {
- returns: Record
- }
+ returns: Record
+ }
? APISchema['returns']
: void
export type APIResponseOptionalContinue =
- | { continue: () => Promise; result: APIResponse }
+ | {continue: () => Promise; result: APIResponse}
| APIResponse
diff --git a/common/src/api/user-types.ts b/common/src/api/user-types.ts
index 2d5e60c5..2ad3aa30 100644
--- a/common/src/api/user-types.ts
+++ b/common/src/api/user-types.ts
@@ -1,6 +1,6 @@
-import { ENV_CONFIG, MOD_USERNAMES } from 'common/envs/constants'
-import { User } from 'common/user'
-import { removeUndefinedProps } from 'common/util/object'
+import {ENV_CONFIG, MOD_USERNAMES} from 'common/envs/constants'
+import {User} from 'common/user'
+import {removeUndefinedProps} from 'common/util/object'
export type DisplayUser = {
id: string
diff --git a/common/src/api/utils.ts b/common/src/api/utils.ts
index 3bdc2c78..6cbc6fae 100644
--- a/common/src/api/utils.ts
+++ b/common/src/api/utils.ts
@@ -1,5 +1,5 @@
import {BACKEND_DOMAIN} from 'common/envs/constants'
-import {IS_LOCAL} from "common/hosting/constants";
+import {IS_LOCAL} from 'common/hosting/constants'
type ErrorCode =
| 400 // your input is bad (like zod is mad)
diff --git a/common/src/api/websocket-client.ts b/common/src/api/websocket-client.ts
index c63af36e..66039317 100644
--- a/common/src/api/websocket-client.ts
+++ b/common/src/api/websocket-client.ts
@@ -1,4 +1,4 @@
-import { ClientMessage, ClientMessageType, ServerMessage } from './websockets'
+import {ClientMessage, ClientMessageType, ServerMessage} from './websockets'
// mqp: useful for debugging
const VERBOSE_LOGGING = false
@@ -13,11 +13,7 @@ type OpenState = typeof WebSocket.OPEN
type ClosingState = typeof WebSocket.CLOSING
type ClosedState = typeof WebSocket.CLOSED
-export type ReadyState =
- | OpenState
- | ConnectingState
- | ClosedState
- | ClosingState
+export type ReadyState = OpenState | ConnectingState | ClosedState | ClosingState
export function formatState(state: ReadyState) {
switch (state) {
@@ -54,7 +50,7 @@ export class APIRealtimeClient {
// subscribers by the topic they are subscribed to
subscriptions: Map
connectTimeout?: NodeJS.Timeout
- heartbeat?: number | undefined;
+ heartbeat?: number | undefined
constructor(url: string) {
this.url = url
@@ -93,9 +89,9 @@ export class APIRealtimeClient {
// Send a heartbeat ping every 25s
this.heartbeat = window.setInterval(() => {
if (this.ws.readyState === WebSocket.OPEN) {
- this.sendMessage('ping', {}).catch(console.error);
+ this.sendMessage('ping', {}).catch(console.error)
}
- }, 25000);
+ }, 25000)
if (this.subscriptions.size > 0) {
this.sendMessage('subscribe', {
topics: Array.from(this.subscriptions.keys()),
@@ -176,7 +172,7 @@ export class APIRealtimeClient {
async sendMessage(
type: T,
- data: Omit, 'type' | 'txid'>
+ data: Omit, 'type' | 'txid'>,
) {
if (VERBOSE_LOGGING) {
console.info(`> Outgoing API websocket ${type} message: `, data)
@@ -188,8 +184,8 @@ export class APIRealtimeClient {
this.txns.delete(txid)
reject(new Error(`Websocket message with txid ${txid} timed out.`))
}, TIMEOUT_MS)
- this.txns.set(txid, { resolve, reject, timeout })
- this.ws.send(JSON.stringify({ type, txid, ...data }))
+ this.txns.set(txid, {resolve, reject, timeout})
+ this.ws.send(JSON.stringify({type, txid, ...data}))
})
} else {
// expected if components in the code try to subscribe or unsubscribe
@@ -199,7 +195,7 @@ export class APIRealtimeClient {
}
async identify(uid: string) {
- return await this.sendMessage('identify', { uid })
+ return await this.sendMessage('identify', {uid})
}
async subscribe(topics: string[], handler: BroadcastHandler) {
@@ -207,7 +203,7 @@ export class APIRealtimeClient {
let existingHandlers = this.subscriptions.get(topic)
if (existingHandlers == null) {
this.subscriptions.set(topic, (existingHandlers = [handler]))
- return await this.sendMessage('subscribe', { topics: [topic] })
+ return await this.sendMessage('subscribe', {topics: [topic]})
} else {
existingHandlers.push(handler)
}
@@ -225,7 +221,7 @@ export class APIRealtimeClient {
this.subscriptions.set(topic, remainingHandlers)
} else {
this.subscriptions.delete(topic)
- return await this.sendMessage('unsubscribe', { topics: [topic] })
+ return await this.sendMessage('unsubscribe', {topics: [topic]})
}
}
}
diff --git a/common/src/api/websockets.ts b/common/src/api/websockets.ts
index 662a8918..04e09dff 100644
--- a/common/src/api/websockets.ts
+++ b/common/src/api/websockets.ts
@@ -1,4 +1,4 @@
-import { z } from 'zod'
+import {z} from 'zod'
export const CLIENT_MESSAGE_SCHEMAS = {
identify: z.object({
@@ -55,11 +55,13 @@ export const SERVER_MESSAGE_SCHEMA = z.union([
])
export type ClientMessageType = keyof typeof CLIENT_MESSAGE_SCHEMAS
-export type ClientMessage =
- z.infer<(typeof CLIENT_MESSAGE_SCHEMAS)[T]>
+export type ClientMessage = z.infer<
+ (typeof CLIENT_MESSAGE_SCHEMAS)[T]
+>
export type ServerMessageType = keyof typeof SERVER_MESSAGE_SCHEMAS
-export type ServerMessage =
- z.infer<(typeof SERVER_MESSAGE_SCHEMAS)[T]>
+export type ServerMessage = z.infer<
+ (typeof SERVER_MESSAGE_SCHEMAS)[T]
+>
export type BroadcastPayload = ServerMessage<'broadcast'>['data']
diff --git a/common/src/api/zod-types.ts b/common/src/api/zod-types.ts
index 5be0e0ea..639aa459 100644
--- a/common/src/api/zod-types.ts
+++ b/common/src/api/zod-types.ts
@@ -1,12 +1,9 @@
-import {z} from 'zod'
import {type JSONContent} from '@tiptap/core'
import {arrify} from 'common/util/array'
+import {z} from 'zod'
/* GET request array can be like ?a=1 or ?a=1&a=2 */
-export const arraybeSchema = z
- .array(z.string())
- .or(z.string())
- .transform(arrify)
+export const arraybeSchema = z.array(z.string()).or(z.string()).transform(arrify)
export const contentSchema: z.ZodType = z.lazy(() =>
z.intersection(
@@ -22,13 +19,13 @@ export const contentSchema: z.ZodType = z.lazy(() =>
z.object({
type: z.string(),
attrs: z.record(z.any()).optional(),
- })
- )
+ }),
+ ),
)
.optional(),
text: z.string().optional(),
- })
- )
+ }),
+ ),
)
const genderType = z.string()
@@ -110,6 +107,4 @@ const optionalProfilesSchema = z.object({
work: z.array(z.string()).optional().nullable(),
})
-export const combinedProfileSchema = baseProfilesSchema.merge(
- optionalProfilesSchema
-)
+export const combinedProfileSchema = baseProfilesSchema.merge(optionalProfilesSchema)
diff --git a/common/src/chat-message.ts b/common/src/chat-message.ts
index f4c4a5fb..7314ad6a 100644
--- a/common/src/chat-message.ts
+++ b/common/src/chat-message.ts
@@ -1,4 +1,4 @@
-import { type JSONContent } from '@tiptap/core'
+import {type JSONContent} from '@tiptap/core'
export type ChatVisibility = 'private' | 'system_status' | 'introduction'
export type ChatMessage = {
diff --git a/common/src/choices.ts b/common/src/choices.ts
index 2d4941aa..35d173f3 100644
--- a/common/src/choices.ts
+++ b/common/src/choices.ts
@@ -1,25 +1,25 @@
-import {invert} from "lodash";
+import {invert} from 'lodash'
export const RELATIONSHIP_CHOICES = {
// Other: 'other',
Collaboration: 'collaboration',
Friendship: 'friendship',
Relationship: 'relationship',
-};
+}
export const RELATIONSHIP_STATUS_CHOICES = {
- 'Single': 'single',
- 'Married': 'married',
+ Single: 'single',
+ Married: 'married',
'In casual relationship': 'casual',
'In long-term relationship': 'long_term',
'In open relationship': 'open',
-};
+}
export const ROMANTIC_CHOICES = {
Monogamous: 'mono',
Polyamorous: 'poly',
'Open Relationship': 'open',
-};
+}
export const POLITICAL_CHOICES = {
Progressive: 'progressive',
@@ -49,133 +49,133 @@ export const DIET_CHOICES = {
export const EDUCATION_CHOICES = {
'High school': 'high-school',
- 'College': 'some-college',
- 'Bachelors': 'bachelors',
- 'Masters': 'masters',
- 'PhD': 'doctorate',
+ College: 'some-college',
+ Bachelors: 'bachelors',
+ Masters: 'masters',
+ PhD: 'doctorate',
}
export const RELIGION_CHOICES = {
- 'Atheist': 'atheist',
- 'Agnostic': 'agnostic',
- 'Spiritual': 'spiritual',
- 'Christian': 'christian',
- 'Muslim': 'muslim',
- 'Jewish': 'jewish',
- 'Hindu': 'hindu',
- 'Buddhist': 'buddhist',
- 'Sikh': 'sikh',
- 'Taoist': 'taoist',
- 'Jain': 'jain',
- 'Shinto': 'shinto',
- 'Animist': 'animist',
- 'Zoroastrian': 'zoroastrian',
+ Atheist: 'atheist',
+ Agnostic: 'agnostic',
+ Spiritual: 'spiritual',
+ Christian: 'christian',
+ Muslim: 'muslim',
+ Jewish: 'jewish',
+ Hindu: 'hindu',
+ Buddhist: 'buddhist',
+ Sikh: 'sikh',
+ Taoist: 'taoist',
+ Jain: 'jain',
+ Shinto: 'shinto',
+ Animist: 'animist',
+ Zoroastrian: 'zoroastrian',
'Unitarian Universalist': 'unitarian_universalist',
- 'Other': 'other',
+ Other: 'other',
}
export const LANGUAGE_CHOICES = {
- 'Akan': 'akan',
- 'Amharic': 'amharic',
- 'Arabic': 'arabic',
- 'Assamese': 'assamese',
- 'Awadhi': 'awadhi',
- 'Azerbaijani': 'azerbaijani',
- 'Balochi': 'balochi',
- 'Belarusian': 'belarusian',
- 'Bengali': 'bengali',
- 'Bhojpuri': 'bhojpuri',
- 'Burmese': 'burmese',
- 'Cebuano': 'cebuano',
- 'Chewa': 'chewa',
- 'Chhattisgarhi': 'chhattisgarhi',
- 'Chittagonian': 'chittagonian',
- 'Czech': 'czech',
- 'Deccan': 'deccan',
- 'Dhundhari': 'dhundhari',
- 'Dutch': 'dutch',
+ Akan: 'akan',
+ Amharic: 'amharic',
+ Arabic: 'arabic',
+ Assamese: 'assamese',
+ Awadhi: 'awadhi',
+ Azerbaijani: 'azerbaijani',
+ Balochi: 'balochi',
+ Belarusian: 'belarusian',
+ Bengali: 'bengali',
+ Bhojpuri: 'bhojpuri',
+ Burmese: 'burmese',
+ Cebuano: 'cebuano',
+ Chewa: 'chewa',
+ Chhattisgarhi: 'chhattisgarhi',
+ Chittagonian: 'chittagonian',
+ Czech: 'czech',
+ Deccan: 'deccan',
+ Dhundhari: 'dhundhari',
+ Dutch: 'dutch',
'Eastern Min': 'eastern-min',
- 'English': 'english',
- 'French': 'french',
- 'Fula': 'fula',
- 'Gan': 'gan',
- 'German': 'german',
- 'Greek': 'greek',
- 'Gujarati': 'gujarati',
+ English: 'english',
+ French: 'french',
+ Fula: 'fula',
+ Gan: 'gan',
+ German: 'german',
+ Greek: 'greek',
+ Gujarati: 'gujarati',
'Haitian Creole': 'haitian-creole',
- 'Hakka': 'hakka',
- 'Haryanvi': 'haryanvi',
- 'Hausa': 'hausa',
- 'Hiligaynon': 'hiligaynon',
- 'Hindi': 'hindi',
- 'Hmong': 'hmong',
- 'Hungarian': 'hungarian',
- 'Igbo': 'igbo',
- 'Ilocano': 'ilocano',
- 'Italian': 'italian',
- 'Japanese': 'japanese',
- 'Javanese': 'javanese',
- 'Jin': 'jin',
- 'Kannada': 'kannada',
- 'Kazakh': 'kazakh',
- 'Khmer': 'khmer',
- 'Kinyarwanda': 'kinyarwanda',
- 'Kirundi': 'kirundi',
- 'Konkani': 'konkani',
- 'Korean': 'korean',
- 'Kurdish': 'kurdish',
- 'Madurese': 'madurese',
- 'Magahi': 'magahi',
- 'Maithili': 'maithili',
- 'Malagasy': 'malagasy',
+ Hakka: 'hakka',
+ Haryanvi: 'haryanvi',
+ Hausa: 'hausa',
+ Hiligaynon: 'hiligaynon',
+ Hindi: 'hindi',
+ Hmong: 'hmong',
+ Hungarian: 'hungarian',
+ Igbo: 'igbo',
+ Ilocano: 'ilocano',
+ Italian: 'italian',
+ Japanese: 'japanese',
+ Javanese: 'javanese',
+ Jin: 'jin',
+ Kannada: 'kannada',
+ Kazakh: 'kazakh',
+ Khmer: 'khmer',
+ Kinyarwanda: 'kinyarwanda',
+ Kirundi: 'kirundi',
+ Konkani: 'konkani',
+ Korean: 'korean',
+ Kurdish: 'kurdish',
+ Madurese: 'madurese',
+ Magahi: 'magahi',
+ Maithili: 'maithili',
+ Malagasy: 'malagasy',
'Malay/Indonesian': 'malay/indonesian',
- 'Malayalam': 'malayalam',
- 'Mandarin': 'mandarin',
- 'Marathi': 'marathi',
- 'Marwari': 'marwari',
- 'Mossi': 'mossi',
- 'Nepali': 'nepali',
+ Malayalam: 'malayalam',
+ Mandarin: 'mandarin',
+ Marathi: 'marathi',
+ Marwari: 'marwari',
+ Mossi: 'mossi',
+ Nepali: 'nepali',
'Northern Min': 'northern-min',
- 'Odia': 'odia',
- 'Oromo': 'oromo',
- 'Pashto': 'pashto',
- 'Persian': 'persian',
- 'Polish': 'polish',
- 'Portuguese': 'portuguese',
- 'Punjabi': 'punjabi',
- 'Quechua': 'quechua',
- 'Romanian': 'romanian',
- 'Russian': 'russian',
- 'Saraiki': 'saraiki',
+ Odia: 'odia',
+ Oromo: 'oromo',
+ Pashto: 'pashto',
+ Persian: 'persian',
+ Polish: 'polish',
+ Portuguese: 'portuguese',
+ Punjabi: 'punjabi',
+ Quechua: 'quechua',
+ Romanian: 'romanian',
+ Russian: 'russian',
+ Saraiki: 'saraiki',
'Serbo-Croatian': 'serbo-croatian',
- 'Shona': 'shona',
- 'Sindhi': 'sindhi',
- 'Sinhala': 'sinhala',
- 'Somali': 'somali',
+ Shona: 'shona',
+ Sindhi: 'sindhi',
+ Sinhala: 'sinhala',
+ Somali: 'somali',
'Southern Min': 'southern-min',
- 'Spanish': 'spanish',
- 'Sundanese': 'sundanese',
- 'Swedish': 'swedish',
- 'Swahili': 'swahili',
- 'Sylheti': 'sylheti',
- 'Tagalog': 'tagalog',
- 'Tamil': 'tamil',
- 'Telugu': 'telugu',
- 'Thai': 'thai',
- 'Turkish': 'turkish',
- 'Turkmen': 'turkmen',
- 'Ukrainian': 'ukrainian',
- 'Urdu': 'urdu',
- 'Uyghur': 'uyghur',
- 'Uzbek': 'uzbek',
- 'Vietnamese': 'vietnamese',
- 'Wu': 'wu',
- 'Xhosa': 'xhosa',
- 'Xiang': 'xiang',
- 'Yoruba': 'yoruba',
- 'Yue': 'yue',
- 'Zhuang': 'zhuang',
- 'Zulu': 'zulu',
+ Spanish: 'spanish',
+ Sundanese: 'sundanese',
+ Swedish: 'swedish',
+ Swahili: 'swahili',
+ Sylheti: 'sylheti',
+ Tagalog: 'tagalog',
+ Tamil: 'tamil',
+ Telugu: 'telugu',
+ Thai: 'thai',
+ Turkish: 'turkish',
+ Turkmen: 'turkmen',
+ Ukrainian: 'ukrainian',
+ Urdu: 'urdu',
+ Uyghur: 'uyghur',
+ Uzbek: 'uzbek',
+ Vietnamese: 'vietnamese',
+ Wu: 'wu',
+ Xhosa: 'xhosa',
+ Xiang: 'xiang',
+ Yoruba: 'yoruba',
+ Yue: 'yue',
+ Zhuang: 'zhuang',
+ Zulu: 'zulu',
}
export const RACE_CHOICES = {
@@ -190,22 +190,22 @@ export const RACE_CHOICES = {
}
export const MBTI_CHOICES = {
- 'INTJ': 'intj',
- 'INTP': 'intp',
- 'INFJ': 'infj',
- 'INFP': 'infp',
- 'ISTJ': 'istj',
- 'ISTP': 'istp',
- 'ISFJ': 'isfj',
- 'ISFP': 'isfp',
- 'ENTJ': 'entj',
- 'ENTP': 'entp',
- 'ENFJ': 'enfj',
- 'ENFP': 'enfp',
- 'ESTJ': 'estj',
- 'ESTP': 'estp',
- 'ESFJ': 'esfj',
- 'ESFP': 'esfp',
+ INTJ: 'intj',
+ INTP: 'intp',
+ INFJ: 'infj',
+ INFP: 'infp',
+ ISTJ: 'istj',
+ ISTP: 'istp',
+ ISFJ: 'isfj',
+ ISFP: 'isfp',
+ ENTJ: 'entj',
+ ENTP: 'entp',
+ ENFJ: 'enfj',
+ ENFP: 'enfp',
+ ESTJ: 'estj',
+ ESTP: 'estp',
+ ESFJ: 'esfj',
+ ESFP: 'esfp',
}
export const GENDERS = {
@@ -230,4 +230,4 @@ export const INVERTED_RELIGION_CHOICES = invert(RELIGION_CHOICES)
export const INVERTED_LANGUAGE_CHOICES = invert(LANGUAGE_CHOICES)
export const INVERTED_RACE_CHOICES = invert(RACE_CHOICES)
export const INVERTED_MBTI_CHOICES = invert(MBTI_CHOICES)
-export const INVERTED_GENDERS = invert(GENDERS)
\ No newline at end of file
+export const INVERTED_GENDERS = invert(GENDERS)
diff --git a/common/src/comment.ts b/common/src/comment.ts
index d277ad9e..994e859c 100644
--- a/common/src/comment.ts
+++ b/common/src/comment.ts
@@ -1,4 +1,4 @@
-import { type JSONContent } from '@tiptap/core'
+import {type JSONContent} from '@tiptap/core'
export const MAX_COMMENT_LENGTH = 10000
@@ -36,4 +36,4 @@ export type Comment = {
isApi?: boolean
}
-export type ReplyToUserInfo = { id: string; username: string }
+export type ReplyToUserInfo = {id: string; username: string}
diff --git a/common/src/constants.ts b/common/src/constants.ts
index 78ff59a7..7d0fc084 100644
--- a/common/src/constants.ts
+++ b/common/src/constants.ts
@@ -4,36 +4,38 @@ export const MAX_INT = Number.MAX_SAFE_INTEGER
export const supportEmail = 'hello@compassmeet.com'
// export const marketingEmail = 'hello@compassmeet.com'
-export const githubRepoSlug = "CompassConnections/Compass"
+export const githubRepoSlug = 'CompassConnections/Compass'
export const githubRepo = `https://github.com/${githubRepoSlug}`
export const githubIssues = `${githubRepo}/issues`
-export const paypalLink = "https://www.paypal.com/paypalme/CompassConnections"
-export const openCollectiveLink = "https://opencollective.com/compass-connection"
-export const liberapayLink = "https://liberapay.com/CompassConnections"
-export const patreonLink = "https://patreon.com/CompassMeet"
-export const discordLink = "https://discord.gg/8Vd7jzqjun"
-export const stoatLink = "https://stt.gg/YKQp81yA"
-export const redditLink = "https://www.reddit.com/r/CompassConnect"
-export const xLink = "https://x.com/compassmeet"
-export const instagramLink = "https://www.instagram.com/compassmeet/"
-export const formLink = "https://forms.gle/tKnXUMAbEreMK6FC6"
-export const ANDROID_APP_URL = 'https://play.google.com/store/apps/details?id=com.compassconnections.app'
+export const paypalLink = 'https://www.paypal.com/paypalme/CompassConnections'
+export const openCollectiveLink = 'https://opencollective.com/compass-connection'
+export const liberapayLink = 'https://liberapay.com/CompassConnections'
+export const patreonLink = 'https://patreon.com/CompassMeet'
+export const discordLink = 'https://discord.gg/8Vd7jzqjun'
+export const stoatLink = 'https://stt.gg/YKQp81yA'
+export const redditLink = 'https://www.reddit.com/r/CompassConnect'
+export const xLink = 'https://x.com/compassmeet'
+export const instagramLink = 'https://www.instagram.com/compassmeet/'
+export const formLink = 'https://forms.gle/tKnXUMAbEreMK6FC6'
+export const ANDROID_APP_URL =
+ 'https://play.google.com/store/apps/details?id=com.compassconnections.app'
export const IS_MAINTENANCE = false // set to true to enable the maintenance mode banner
export const MIN_BIO_LENGTH = 250
-export const WEB_GOOGLE_CLIENT_ID = '253367029065-khkj31qt22l0vc3v754h09vhpg6t33ad.apps.googleusercontent.com'
+export const WEB_GOOGLE_CLIENT_ID =
+ '253367029065-khkj31qt22l0vc3v754h09vhpg6t33ad.apps.googleusercontent.com'
// export const ANDROID_GOOGLE_CLIENT_ID = '253367029065-s9sr5vqgkhc8f7p5s6ti6a4chqsrqgc4.apps.googleusercontent.com'
export const GOOGLE_CLIENT_ID = WEB_GOOGLE_CLIENT_ID
export const defaultLocale = 'en'
export const LOCALES = {
- en: "English",
- fr: "Français",
- de: "Deutsch",
+ en: 'English',
+ fr: 'Français',
+ de: 'Deutsch',
// es: "Español",
}
export const supportedLocales = Object.keys(LOCALES)
-export type Locale = typeof supportedLocales[number]
\ No newline at end of file
+export type Locale = (typeof supportedLocales)[number]
diff --git a/common/src/discord/core.ts b/common/src/discord/core.ts
index 1ae0a4e6..c9523688 100644
--- a/common/src/discord/core.ts
+++ b/common/src/discord/core.ts
@@ -1,4 +1,4 @@
-import {IS_DEV} from "common/envs/constants";
+import {IS_DEV} from 'common/envs/constants'
export const sendDiscordMessage = async (content: string, channel: string) => {
let webhookUrl = {
@@ -25,4 +25,4 @@ export const sendDiscordMessage = async (content: string, channel: string) => {
const text = await response.text()
console.error(text)
}
-}
\ No newline at end of file
+}
diff --git a/common/src/edge/og.ts b/common/src/edge/og.ts
index 7ab8089b..3b987039 100644
--- a/common/src/edge/og.ts
+++ b/common/src/edge/og.ts
@@ -1,6 +1,6 @@
// see https://vercel.com/docs/concepts/functions/edge-functions/edge-functions-api for restrictions
-export type Point = { x: number; y: number }
+export type Point = {x: number; y: number}
export function base64toPoints(base64urlString: string) {
const b64 = base64urlString.replace(/-/g, '+').replace(/_/g, '/')
@@ -8,9 +8,9 @@ export function base64toPoints(base64urlString: string) {
const u = Uint8Array.from(bin, (c) => c.charCodeAt(0))
const f = new Float32Array(u.buffer)
- const points = [] as { x: number; y: number }[]
+ const points = [] as {x: number; y: number}[]
for (let i = 0; i < f.length; i += 2) {
- points.push({ x: f[i], y: f[i + 1] })
+ points.push({x: f[i], y: f[i + 1]})
}
return points
}
diff --git a/common/src/envs/constants.ts b/common/src/envs/constants.ts
index 368556f4..b47226d2 100644
--- a/common/src/envs/constants.ts
+++ b/common/src/envs/constants.ts
@@ -1,7 +1,13 @@
+import {isProd} from 'common/envs/is-prod'
+import {
+ HOSTING_ENV,
+ IS_LOCAL,
+ IS_LOCAL_ANDROID,
+ IS_WEBVIEW_DEV_PHONE,
+} from 'common/hosting/constants'
+
import {DEV_CONFIG} from './dev'
import {PROD_CONFIG} from './prod'
-import {isProd} from "common/envs/is-prod";
-import {HOSTING_ENV, IS_LOCAL, IS_LOCAL_ANDROID, IS_WEBVIEW_DEV_PHONE} from "common/hosting/constants";
export const MAX_DESCRIPTION_LENGTH = 100000
export const MAX_ANSWER_LENGTH = 240
@@ -20,7 +26,7 @@ export const ENV = isProd() ? 'prod' : 'dev'
// export const IS_PROD = ENV === 'prod'
export const IS_DEV = ENV === 'dev'
-console.debug(`Running in ${HOSTING_ENV} (${ENV})`,);
+console.debug(`Running in ${HOSTING_ENV} (${ENV})`)
// class MissingKeyError implements Error {
// constructor(key: string) {
@@ -41,7 +47,7 @@ console.debug(`Running in ${HOSTING_ENV} (${ENV})`,);
// throw new MissingKeyError('firebaseConfig.apiKey')
// }
-export const IS_FIREBASE_EMULATOR = process.env.NEXT_PUBLIC_FIREBASE_EMULATOR === "true"
+export const IS_FIREBASE_EMULATOR = process.env.NEXT_PUBLIC_FIREBASE_EMULATOR === 'true'
// if (IS_FIREBASE_EMULATOR) console.log("Using Firebase emulator.")
export const LOCAL_WEB_DOMAIN = `localhost:3000`
@@ -56,18 +62,11 @@ export const PROJECT_ID = ENV_CONFIG.firebaseConfig.projectId
export const REDIRECT_URI = `${WEB_URL}/auth/callback`
-export const AUTH_COOKIE_NAME = `FBUSER_${PROJECT_ID.toUpperCase().replace(
- /-/g,
- '_'
-)}`
+export const AUTH_COOKIE_NAME = `FBUSER_${PROJECT_ID.toUpperCase().replace(/-/g, '_')}`
-export const MOD_USERNAMES = [
- 'Martin',
-]
+export const MOD_USERNAMES = ['Martin']
-export const VERIFIED_USERNAMES = [
- 'Martin',
-]
+export const VERIFIED_USERNAMES = ['Martin']
export const TEN_YEARS_SECS = 60 * 60 * 24 * 365 * 10
@@ -160,4 +159,4 @@ export const RESERVED_PATHS = [
export function getStorageBucketId() {
return ENV_CONFIG.firebaseConfig.storageBucket
-}
\ No newline at end of file
+}
diff --git a/common/src/envs/dev.ts b/common/src/envs/dev.ts
index 788628aa..122132d8 100644
--- a/common/src/envs/dev.ts
+++ b/common/src/envs/dev.ts
@@ -1,4 +1,4 @@
-import { EnvConfig, PROD_CONFIG } from './prod'
+import {EnvConfig, PROD_CONFIG} from './prod'
export const DEV_CONFIG: EnvConfig = {
...PROD_CONFIG,
@@ -7,17 +7,18 @@ export const DEV_CONFIG: EnvConfig = {
supabaseInstanceId: 'zbspxezubpzxmuxciurg',
dbEncryptionKey: 'MIWD5LuZ6KoRNChcmOn762MKJRHhH4rLUGYse7+GeS4=',
supabasePwd: 'ZTNlifGKofSKhu8c', // For database write access (dev). A 16-character password with digits and letters.
- supabaseAnonKey: 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inpic3B4ZXp1YnB6eG11eGNpdXJnIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTc2ODM0MTMsImV4cCI6MjA3MzI1OTQxM30.ZkM7zlawP8Nke0T3KJrqpOQ4DzqPaXTaJXLC2WU8Y7c',
+ supabaseAnonKey:
+ 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6Inpic3B4ZXp1YnB6eG11eGNpdXJnIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NTc2ODM0MTMsImV4cCI6MjA3MzI1OTQxM30.ZkM7zlawP8Nke0T3KJrqpOQ4DzqPaXTaJXLC2WU8Y7c',
googleApplicationCredentials: 'googleApplicationCredentials-dev.json',
firebaseConfig: {
- apiKey: "AIzaSyBspL9glBXWbMsjmtt36dgb2yU0YGGhzKo",
- authDomain: "compass-57c3c.firebaseapp.com",
- projectId: "compass-57c3c",
- storageBucket: "compass-57c3c.firebasestorage.app",
+ apiKey: 'AIzaSyBspL9glBXWbMsjmtt36dgb2yU0YGGhzKo',
+ authDomain: 'compass-57c3c.firebaseapp.com',
+ projectId: 'compass-57c3c',
+ storageBucket: 'compass-57c3c.firebasestorage.app',
privateBucket: 'compass-130ba-private',
- messagingSenderId: "297460199314",
- appId: "1:297460199314:web:c45678c54285910e255b4b",
- measurementId: "G-N6LZ64EMJ2",
+ messagingSenderId: '297460199314',
+ appId: '1:297460199314:web:c45678c54285910e255b4b',
+ measurementId: 'G-N6LZ64EMJ2',
region: 'us-west1',
},
adminIds: [
diff --git a/common/src/envs/prod.ts b/common/src/envs/prod.ts
index f71ed726..031a1e42 100644
--- a/common/src/envs/prod.ts
+++ b/common/src/envs/prod.ts
@@ -43,13 +43,13 @@ export const PROD_CONFIG: EnvConfig = {
googleApplicationCredentials: undefined,
firebaseConfig: {
apiKey: '',
- authDomain: "compass-130ba.firebaseapp.com",
- projectId: "compass-130ba",
- storageBucket: "compass-130ba.firebasestorage.app",
+ authDomain: 'compass-130ba.firebaseapp.com',
+ projectId: 'compass-130ba',
+ storageBucket: 'compass-130ba.firebasestorage.app',
privateBucket: 'compass-130ba-private',
- messagingSenderId: "253367029065",
- appId: "1:253367029065:web:b338785af99d4145095e98",
- measurementId: "G-2LSQYJQE6P",
+ messagingSenderId: '253367029065',
+ appId: '1:253367029065:web:b338785af99d4145095e98',
+ measurementId: 'G-2LSQYJQE6P',
region: 'us-west1',
},
cloudRunId: 'w3txbmd3ba',
diff --git a/common/src/filters.ts b/common/src/filters.ts
index c7fec93e..c1e80de3 100644
--- a/common/src/filters.ts
+++ b/common/src/filters.ts
@@ -1,7 +1,7 @@
-import {Profile, ProfileRow} from "common/profiles/profile";
-import {cloneDeep} from "lodash";
-import {filterDefined} from "common/util/array";
-import {OptionTableKey} from "common/profiles/constants";
+import {OptionTableKey} from 'common/profiles/constants'
+import {Profile, ProfileRow} from 'common/profiles/profile'
+import {filterDefined} from 'common/util/array'
+import {cloneDeep} from 'lodash'
// export type TargetArea = {
// lat: number
@@ -35,28 +35,24 @@ export type FilterFields = {
big5_neuroticism_max: number | undefined
} & {
[K in OptionTableKey]: string[]
-}
- & Pick<
- ProfileRow,
- | 'wants_kids_strength'
- | 'pref_relation_styles'
- | 'pref_romantic_styles'
- | 'diet'
- | 'political_beliefs'
- | 'relationship_status'
- | 'languages'
- | 'is_smoker'
- | 'has_kids'
- | 'pref_gender'
- | 'pref_age_min'
- | 'pref_age_max'
- | 'religion'
->
+} & Pick<
+ ProfileRow,
+ | 'wants_kids_strength'
+ | 'pref_relation_styles'
+ | 'pref_romantic_styles'
+ | 'diet'
+ | 'political_beliefs'
+ | 'relationship_status'
+ | 'languages'
+ | 'is_smoker'
+ | 'has_kids'
+ | 'pref_gender'
+ | 'pref_age_min'
+ | 'pref_age_max'
+ | 'religion'
+ >
-export const orderProfiles = (
- profiles: Profile[],
- starredUserIds: string[] | undefined
-) => {
+export const orderProfiles = (profiles: Profile[], starredUserIds: string[] | undefined) => {
if (!profiles) return
let s = cloneDeep(profiles)
@@ -113,7 +109,6 @@ export const initialFilters: Partial = {
orderBy: 'created_time',
}
-
export const FilterKeys = Object.keys(initialFilters) as (keyof FilterFields)[]
-export type OriginLocation = { id: string; name: string | null, lat: number, lon: number }
+export type OriginLocation = {id: string; name: string | null; lat: number; lon: number}
diff --git a/common/src/gender.ts b/common/src/gender.ts
index 0e8e0bae..e99ac2de 100644
--- a/common/src/gender.ts
+++ b/common/src/gender.ts
@@ -1,9 +1,4 @@
-export type Gender =
- | 'male'
- | 'female'
- | 'non-binary'
- | 'trans-male'
- | 'trans-female'
+export type Gender = 'male' | 'female' | 'non-binary' | 'trans-male' | 'trans-female'
export function convertGender(gender: Gender) {
if (gender == 'male') {
diff --git a/common/src/geodb.ts b/common/src/geodb.ts
index c0d27e00..911705b4 100644
--- a/common/src/geodb.ts
+++ b/common/src/geodb.ts
@@ -1,4 +1,3 @@
-
export const geodbHost = 'wft-geo-db.p.rapidapi.com'
export const geodbFetch = async (endpoint: string) => {
@@ -29,4 +28,4 @@ export const geodbFetch = async (endpoint: string) => {
console.debug('geodbFetch', endpoint, error)
return {status: 'failure', data: error}
}
-}
\ No newline at end of file
+}
diff --git a/common/src/has-kids.ts b/common/src/has-kids.ts
index d1ca0c6c..52214ddb 100644
--- a/common/src/has-kids.ts
+++ b/common/src/has-kids.ts
@@ -30,22 +30,16 @@ export const hasKidsNames = Object.values(hasKidsLabels).reduce => {
- return Object.values(labels).reduce(
- (acc: Record, label: HasKidLabel) => {
- acc[label.name] = label.value
- return acc
- },
- {}
- )
+export const generateChoicesMap = (labels: HasKidsLabelsMap): Record => {
+ return Object.values(labels).reduce((acc: Record, label: HasKidLabel) => {
+ acc[label.name] = label.value
+ return acc
+ }, {})
}
// export const NO_PREFERENCE_STRENGTH = -1
// export const WANTS_KIDS_STRENGTH = 2
-// export const DOESNT_WANT_KIDS_STRENGTH = 0
\ No newline at end of file
+// export const DOESNT_WANT_KIDS_STRENGTH = 0
diff --git a/common/src/hosting/constants.ts b/common/src/hosting/constants.ts
index ab6a5977..7dd97da4 100644
--- a/common/src/hosting/constants.ts
+++ b/common/src/hosting/constants.ts
@@ -1,15 +1,24 @@
export const IS_WEBVIEW_DEV_PHONE = process.env.NEXT_PUBLIC_WEBVIEW_DEV_PHONE === '1'
-export const IS_LOCAL_ANDROID = IS_WEBVIEW_DEV_PHONE || process.env.NEXT_PUBLIC_LOCAL_ANDROID === '1'
+export const IS_LOCAL_ANDROID =
+ IS_WEBVIEW_DEV_PHONE || process.env.NEXT_PUBLIC_LOCAL_ANDROID === '1'
export const IS_WEBVIEW = !!process.env.NEXT_PUBLIC_WEBVIEW
export const IS_GOOGLE_CLOUD = !!process.env.GOOGLE_CLOUD_PROJECT
export const IS_VERCEL = !!process.env.NEXT_PUBLIC_VERCEL
export const IS_DEPLOYED = IS_GOOGLE_CLOUD || IS_VERCEL || IS_WEBVIEW
export const IS_LOCAL = !IS_DEPLOYED
-export const HOSTING_ENV = IS_GOOGLE_CLOUD ? 'Google Cloud' : IS_VERCEL ? 'Vercel' : IS_WEBVIEW ? 'WebView' : IS_LOCAL ? 'local' : 'unknown'
+export const HOSTING_ENV = IS_GOOGLE_CLOUD
+ ? 'Google Cloud'
+ : IS_VERCEL
+ ? 'Vercel'
+ : IS_WEBVIEW
+ ? 'WebView'
+ : IS_LOCAL
+ ? 'local'
+ : 'unknown'
if (IS_LOCAL && !process.env.ENVIRONMENT && !process.env.NEXT_PUBLIC_FIREBASE_ENV) {
- console.warn("No ENVIRONMENT set, defaulting to DEV")
+ console.warn('No ENVIRONMENT set, defaulting to DEV')
process.env.ENVIRONMENT = 'DEV'
}
-// console.log('IS_LOCAL_ANDROID', IS_LOCAL_ANDROID)
\ No newline at end of file
+// console.log('IS_LOCAL_ANDROID', IS_LOCAL_ANDROID)
diff --git a/common/src/logging.ts b/common/src/logging.ts
index d892b4a6..f20f7c62 100644
--- a/common/src/logging.ts
+++ b/common/src/logging.ts
@@ -1,62 +1,62 @@
-import {IS_LOCAL} from "common/hosting/constants"
+import {IS_LOCAL} from 'common/hosting/constants'
class Logger {
- private readonly isLocal: boolean;
+ private readonly isLocal: boolean
constructor(isLocal = false) {
- this.isLocal = isLocal;
- }
-
- private getCallerFrame(skip = 2): string | undefined {
- // Create an Error to get stack trace
- const err = new Error();
- if (!err.stack) return undefined;
-
- const lines = err.stack.split("\n");
-
- // skip frames (0: Error, 1: Logger method, 2+: caller)
- return lines[skip]?.trim();
+ this.isLocal = isLocal
}
trace(...args: any[]) {
// Does not seem to really work. Was trying to show the real file where the log was called from.
- if (!this.isLocal) return;
+ if (!this.isLocal) return
- const caller = this.getCallerFrame(3); // skip Logger frames
+ const caller = this.getCallerFrame(3) // skip Logger frames
if (caller) {
- console.log("Trace:", ...args, "\nCalled from:", caller);
+ console.log('Trace:', ...args, '\nCalled from:', caller)
} else {
- console.trace(...args);
+ console.trace(...args)
}
}
log(...args: any[]) {
- if (this.isLocal) console.log(...args);
+ if (this.isLocal) console.log(...args)
}
info(...args: any[]) {
- if (this.isLocal) console.info(...args);
+ if (this.isLocal) console.info(...args)
}
warn(...args: any[]) {
- if (this.isLocal) console.warn(...args);
+ if (this.isLocal) console.warn(...args)
}
error(...args: any[]) {
- if (this.isLocal) console.error(...args);
+ if (this.isLocal) console.error(...args)
}
debug(...args: any[]) {
- if (this.isLocal) console.debug(...args);
+ if (this.isLocal) console.debug(...args)
}
table(...args: any[]) {
- if (this.isLocal) console.table(...args);
+ if (this.isLocal) console.table(...args)
}
group(...args: any[]) {
- if (this.isLocal) console.group(...args);
+ if (this.isLocal) console.group(...args)
+ }
+
+ private getCallerFrame(skip = 2): string | undefined {
+ // Create an Error to get stack trace
+ const err = new Error()
+ if (!err.stack) return undefined
+
+ const lines = err.stack.split('\n')
+
+ // skip frames (0: Error, 1: Logger method, 2+: caller)
+ return lines[skip]?.trim()
}
}
-export const logger = new Logger(IS_LOCAL)
\ No newline at end of file
+export const logger = new Logger(IS_LOCAL)
diff --git a/common/src/md.ts b/common/src/md.ts
index 8ec65c8e..abf6ef7f 100644
--- a/common/src/md.ts
+++ b/common/src/md.ts
@@ -1,4 +1,4 @@
-import type { JSONContent } from '@tiptap/core'
+import type {JSONContent} from '@tiptap/core'
export function jsonToMarkdown(node: JSONContent): string {
if (!node) return ''
@@ -49,10 +49,12 @@ export function jsonToMarkdown(node: JSONContent): string {
case 'listItem':
return `- ${content}`
case 'blockquote':
- return content
- .split('\n')
- .map((line) => (line ? `> ${line}` : ''))
- .join('\n') + '\n\n'
+ return (
+ content
+ .split('\n')
+ .map((line) => (line ? `> ${line}` : ''))
+ .join('\n') + '\n\n'
+ )
case 'codeBlock':
return `\`\`\`\n${content}\n\`\`\`\n\n`
case 'horizontalRule':
diff --git a/common/src/measurement-utils.ts b/common/src/measurement-utils.ts
index 0d56934e..76b93827 100644
--- a/common/src/measurement-utils.ts
+++ b/common/src/measurement-utils.ts
@@ -7,10 +7,7 @@ export type MeasurementSystem = 'metric' | 'imperial'
/**
* Format height in inches according to the specified measurement system
*/
-export function formatHeight(
- heightInInches: number,
- measurementSystem: MeasurementSystem
-): string {
+export function formatHeight(heightInInches: number, measurementSystem: MeasurementSystem): string {
if (measurementSystem === 'metric') {
// Convert to centimeters
const cm = Math.round(heightInInches * INCHES_TO_CM)
@@ -28,7 +25,7 @@ export function formatHeight(
*/
export function formatDistance(
distanceInMiles: number,
- measurementSystem: MeasurementSystem
+ measurementSystem: MeasurementSystem,
): string {
if (measurementSystem === 'metric') {
// Convert to kilometers
diff --git a/common/src/notifications.ts b/common/src/notifications.ts
index e45810fe..4b732983 100644
--- a/common/src/notifications.ts
+++ b/common/src/notifications.ts
@@ -10,7 +10,7 @@ export type NotificationTemplate = {
sourceUserAvatarUrl?: string
sourceUpdateType?: 'created' | 'updated' | 'deleted'
createdTime: number
- data?: { [key: string]: any }
+ data?: {[key: string]: any}
}
// User-specific notification data (lightweight - references template)
@@ -41,7 +41,7 @@ export type Notification = {
sourceUserUsername?: string
sourceUserAvatarUrl?: string
sourceText: string
- data?: { [key: string]: any }
+ data?: {[key: string]: any}
sourceContractTitle?: string
sourceContractCreatorUsername?: string
@@ -65,31 +65,23 @@ export type Notification = {
export const NOTIFICATIONS_PER_PAGE = 30
-export async function getNotifications(
- db: SupabaseClient,
- userId: string,
- limit: number
-) {
- const { data } = await db
+export async function getNotifications(db: SupabaseClient, userId: string, limit: number) {
+ const {data} = await db
.from('user_notifications')
.select('*')
.eq('user_id', userId)
- .order('data->createdTime', { ascending: false } as any)
+ .order('data->createdTime', {ascending: false} as any)
.limit(limit)
return data?.map((d: Row<'user_notifications'>) => d)
}
-export async function getUnseenNotifications(
- db: SupabaseClient,
- userId: string,
- limit: number
-) {
- const { data } = await db
+export async function getUnseenNotifications(db: SupabaseClient, userId: string, limit: number) {
+ const {data} = await db
.from('user_notifications')
.select('*')
.eq('user_id', userId)
.eq('data->>isSeen', 'false')
- .order('data->createdTime', { ascending: false } as any)
+ .order('data->createdTime', {ascending: false} as any)
.limit(limit)
return data?.map((d: Row<'user_notifications'>) => d) ?? []
diff --git a/common/src/parsing.ts b/common/src/parsing.ts
index 7170e8fc..aaf5904c 100644
--- a/common/src/parsing.ts
+++ b/common/src/parsing.ts
@@ -4,10 +4,10 @@ export const toKey = (str: string | number | boolean) => {
export function trimStrings>(body: T): T {
for (const key in body) {
- const value = (body[key] as unknown | string)
+ const value = body[key] as unknown | string
if (typeof value === 'string') {
body[key] = value.trim() as T[typeof key]
}
}
return body
-}
\ No newline at end of file
+}
diff --git a/common/src/profiles/bookmarked_searches.ts b/common/src/profiles/bookmarked_searches.ts
index 3e59dd89..324a581f 100644
--- a/common/src/profiles/bookmarked_searches.ts
+++ b/common/src/profiles/bookmarked_searches.ts
@@ -10,17 +10,17 @@ export interface MatchUser {
export interface MatchesType {
description: {
- filters: any; // You might want to replace 'any' with a more specific type
- location: any; // You might want to replace 'any' with a more specific type
- };
- matches: MatchUser[]; // You might want to replace 'any' with a more specific type
+ filters: any // You might want to replace 'any' with a more specific type
+ location: any // You might want to replace 'any' with a more specific type
+ }
+ matches: MatchUser[] // You might want to replace 'any' with a more specific type
id: string
}
export interface MatchesByUserType {
[key: string]: {
- user: any;
- privateUser: any;
- matches: MatchesType[];
+ user: any
+ privateUser: any
+ matches: MatchesType[]
}
-}
\ No newline at end of file
+}
diff --git a/common/src/profiles/compatibility-score.ts b/common/src/profiles/compatibility-score.ts
index eb15910d..84098d66 100644
--- a/common/src/profiles/compatibility-score.ts
+++ b/common/src/profiles/compatibility-score.ts
@@ -1,6 +1,7 @@
-import {keyBy, sumBy} from 'lodash'
import {ProfileRow} from 'common/profiles/profile'
import {Row as rowFor} from 'common/supabase/utils'
+import {keyBy, sumBy} from 'lodash'
+
import {
areAgeCompatible,
areLocationCompatible,
@@ -13,7 +14,7 @@ const importanceToScore = {
1: 1,
2: 5,
3: 25,
-} as { [importance: string]: number }
+} as {[importance: string]: number}
export type CompatibilityScore = {
score: number
@@ -22,32 +23,32 @@ export type CompatibilityScore = {
export const getCompatibilityScore = (
answers1: rowFor<'compatibility_answers'>[],
- answers2: rowFor<'compatibility_answers'>[]
+ answers2: rowFor<'compatibility_answers'>[],
): CompatibilityScore => {
- const {score: score1, maxScore: maxScore1, answerCount} = getAnswersCompatibility(answers1, answers2)
+ const {
+ score: score1,
+ maxScore: maxScore1,
+ answerCount,
+ } = getAnswersCompatibility(answers1, answers2)
const {score: score2, maxScore: maxScore2} = getAnswersCompatibility(answers2, answers1)
// >=100 answers in common leads to no weight toward 50%.
// Use sqrt for diminishing returns to answering more questions.
- const weightTowardFiftyPercent = Math.max(
- 25 - 2.5 * Math.sqrt(answerCount),
- 0
- )
+ const weightTowardFiftyPercent = Math.max(25 - 2.5 * Math.sqrt(answerCount), 0)
const upWeight = weightTowardFiftyPercent / 2
const downWeight = weightTowardFiftyPercent
const compat1 = (score1 + upWeight) / (maxScore1 + downWeight)
const compat2 = (score2 + upWeight) / (maxScore2 + downWeight)
const geometricMean = Math.sqrt(compat1 * compat2)
- const confidence =
- answerCount < 10 ? 'low' : answerCount < 100 ? 'medium' : 'high'
+ const confidence = answerCount < 10 ? 'low' : answerCount < 100 ? 'medium' : 'high'
return {score: geometricMean, confidence}
}
const getAnswersCompatibility = (
answers1: rowFor<'compatibility_answers'>[],
- answers2: rowFor<'compatibility_answers'>[]
+ answers2: rowFor<'compatibility_answers'>[],
) => {
const answers2ByQuestionId = keyBy(answers2, 'question_id')
let maxScore = 0
@@ -71,48 +72,37 @@ const getAnswersCompatibility = (
export function getAnswerCompatibilityImportanceScore(
answer1: rowFor<'compatibility_answers'>,
- answer2: rowFor<'compatibility_answers'>
+ answer2: rowFor<'compatibility_answers'>,
) {
const importanceScore = importanceToScore[answer1.importance] ?? 0
- return answer1.pref_choices.includes(answer2.multiple_choice)
- ? importanceScore
- : 0
+ return answer1.pref_choices.includes(answer2.multiple_choice) ? importanceScore : 0
}
export function getAnswerCompatibility(
answer1: rowFor<'compatibility_answers'>,
- answer2: rowFor<'compatibility_answers'>
+ answer2: rowFor<'compatibility_answers'>,
) {
if (answer1.importance < 0 || answer2.importance < 0) {
return false
}
- const compatibility1to2 = answer1.pref_choices.includes(
- answer2.multiple_choice
- )
- const compatibility2to1 = answer2.pref_choices.includes(
- answer1.multiple_choice
- )
+ const compatibility1to2 = answer1.pref_choices.includes(answer2.multiple_choice)
+ const compatibility2to1 = answer2.pref_choices.includes(answer1.multiple_choice)
return compatibility1to2 && compatibility2to1
}
export function getScoredAnswerCompatibility(
answer1: rowFor<'compatibility_answers'>,
- answer2: rowFor<'compatibility_answers'>
+ answer2: rowFor<'compatibility_answers'>,
) {
if (answer1.importance < 0 || answer2.importance < 0) {
return 0
}
- const compatibility1to2 = +answer1.pref_choices.includes(
- answer2.multiple_choice
- )
- const compatibility2to1 = +answer2.pref_choices.includes(
- answer1.multiple_choice
- )
- const importanceCompatibility =
- 1 - Math.abs(answer1.importance - answer2.importance) / 4
+ const compatibility1to2 = +answer1.pref_choices.includes(answer2.multiple_choice)
+ const compatibility2to1 = +answer2.pref_choices.includes(answer1.multiple_choice)
+ const importanceCompatibility = 1 - Math.abs(answer1.importance - answer2.importance) / 4
// Adjust these weights to change the impact of each component
const compatibilityWeight = 0.7
@@ -125,10 +115,7 @@ export function getScoredAnswerCompatibility(
)
}
-export const getProfilesCompatibilityFactor = (
- profile1: ProfileRow,
- profile2: ProfileRow
-) => {
+export const getProfilesCompatibilityFactor = (profile1: ProfileRow, profile2: ProfileRow) => {
let multiplier = 1
multiplier *= areAgeCompatible(profile1, profile2) ? 1 : 0.5
multiplier *= areRelationshipStyleCompatible(profile1, profile2) ? 1 : 0.5
@@ -137,9 +124,7 @@ export const getProfilesCompatibilityFactor = (
return multiplier
}
-export const hasAnsweredQuestions = (
- questions: rowFor<'compatibility_answers'>[],
-) => {
+export const hasAnsweredQuestions = (questions: rowFor<'compatibility_answers'>[]) => {
if (!questions?.length) return false
for (const question of questions) {
if (question.importance >= 0) return true
diff --git a/common/src/profiles/compatibility-util.ts b/common/src/profiles/compatibility-util.ts
index ec15fc3c..e78bc661 100644
--- a/common/src/profiles/compatibility-util.ts
+++ b/common/src/profiles/compatibility-util.ts
@@ -1,5 +1,5 @@
-import { ProfileRow } from 'common/profiles/profile'
-import {MAX_INT, MIN_INT} from "common/constants";
+import {MAX_INT, MIN_INT} from 'common/constants'
+import {ProfileRow} from 'common/profiles/profile'
const isPreferredGender = (
preferredGenders: string[] | undefined | null,
@@ -27,14 +27,14 @@ export const areGenderCompatible = (profile1: ProfileRow, profile2: ProfileRow)
}
const satisfiesAgeRange = (profile: ProfileRow, age: number | null | undefined) => {
- return (age ?? MAX_INT) >= (profile.pref_age_min ?? MIN_INT) && (age ?? MIN_INT) <= (profile.pref_age_max ?? MAX_INT)
+ return (
+ (age ?? MAX_INT) >= (profile.pref_age_min ?? MIN_INT) &&
+ (age ?? MIN_INT) <= (profile.pref_age_max ?? MAX_INT)
+ )
}
export const areAgeCompatible = (profile1: ProfileRow, profile2: ProfileRow) => {
- return (
- satisfiesAgeRange(profile1, profile2.age) &&
- satisfiesAgeRange(profile2, profile1.age)
- )
+ return satisfiesAgeRange(profile1, profile2.age) && satisfiesAgeRange(profile2, profile1.age)
}
export const areLocationCompatible = (profile1: ProfileRow, profile2: ProfileRow) => {
@@ -55,19 +55,16 @@ export const areLocationCompatible = (profile1: ProfileRow, profile2: ProfileRow
return root < 2.5
}
-export const areRelationshipStyleCompatible = (
- profile1: ProfileRow,
- profile2: ProfileRow
-) => {
+export const areRelationshipStyleCompatible = (profile1: ProfileRow, profile2: ProfileRow) => {
if (!profile1.pref_relation_styles?.length || !profile2.pref_relation_styles) return true
return profile1.pref_relation_styles.some((style) =>
- profile2.pref_relation_styles?.includes(style)
+ profile2.pref_relation_styles?.includes(style),
)
}
export const areWantKidsCompatible = (profile1: ProfileRow, profile2: ProfileRow) => {
- const { wants_kids_strength: kids1 } = profile1
- const { wants_kids_strength: kids2 } = profile2
+ const {wants_kids_strength: kids1} = profile1
+ const {wants_kids_strength: kids2} = profile2
if (kids1 == null || kids2 == null) return true
diff --git a/common/src/profiles/constants.ts b/common/src/profiles/constants.ts
index 4e08e259..a7c122e7 100644
--- a/common/src/profiles/constants.ts
+++ b/common/src/profiles/constants.ts
@@ -7,4 +7,4 @@ export const compassUserId = isProd()
export const MAX_COMPATIBILITY_QUESTION_LENGTH = 240
export const OPTION_TABLES = ['interests', 'causes', 'work'] as const
-export type OptionTableKey = typeof OPTION_TABLES[number]
+export type OptionTableKey = (typeof OPTION_TABLES)[number]
diff --git a/common/src/profiles/og-image.ts b/common/src/profiles/og-image.ts
index 22439b36..1b44ee32 100644
--- a/common/src/profiles/og-image.ts
+++ b/common/src/profiles/og-image.ts
@@ -1,6 +1,6 @@
-import { User } from 'common/user'
-import { ProfileRow } from 'common/profiles/profile'
-import { buildOgUrl } from 'common/util/og'
+import {ProfileRow} from 'common/profiles/profile'
+import {User} from 'common/user'
+import {buildOgUrl} from 'common/util/og'
// TODO: handle age, gender undefined better
export type ogProps = {
diff --git a/common/src/profiles/profile.ts b/common/src/profiles/profile.ts
index fcc8a03d..2fa84eaa 100644
--- a/common/src/profiles/profile.ts
+++ b/common/src/profiles/profile.ts
@@ -1,46 +1,35 @@
+import {OptionTableKey} from 'common/profiles/constants'
import {Row, run, SupabaseClient} from 'common/supabase/utils'
import {User} from 'common/user'
-import {OptionTableKey} from "common/profiles/constants";
export type ProfileRow = Row<'profiles'>
export type ProfileWithoutUser = ProfileRow & {[K in OptionTableKey]?: string[]}
-export type Profile = ProfileWithoutUser & { user: User }
+export type Profile = ProfileWithoutUser & {user: User}
-export const getProfileRow = async (userId: string, db: SupabaseClient): Promise => {
+export const getProfileRow = async (
+ userId: string,
+ db: SupabaseClient,
+): Promise => {
// Fetch profile
- const profileRes = await run(
- db
- .from('profiles')
- .select('*')
- .eq('user_id', userId)
- )
+ const profileRes = await run(db.from('profiles').select('*').eq('user_id', userId))
const profile = profileRes.data?.[0]
if (!profile) return null
// Fetch interests
const interestsRes = await run(
- db
- .from('profile_interests')
- .select('interests(name, id)')
- .eq('profile_id', profile.id)
+ db.from('profile_interests').select('interests(name, id)').eq('profile_id', profile.id),
)
const interests = interestsRes.data?.map((row: any) => String(row.interests.id)) || []
// Fetch causes
const causesRes = await run(
- db
- .from('profile_causes')
- .select('causes(name, id)')
- .eq('profile_id', profile.id)
+ db.from('profile_causes').select('causes(name, id)').eq('profile_id', profile.id),
)
const causes = causesRes.data?.map((row: any) => String(row.causes.id)) || []
// Fetch causes
const workRes = await run(
- db
- .from('profile_work')
- .select('work(name, id)')
- .eq('profile_id', profile.id)
+ db.from('profile_work').select('work(name, id)').eq('profile_id', profile.id),
)
const work = workRes.data?.map((row: any) => String(row.work.id)) || []
diff --git a/common/src/searches.ts b/common/src/searches.ts
index 2f69ec78..d08c8adb 100644
--- a/common/src/searches.ts
+++ b/common/src/searches.ts
@@ -1,8 +1,4 @@
// Define nice labels for each key
-import {FilterFields, initialFilters} from 'common/filters'
-import {wantsKidsNames} from 'common/wants-kids'
-import {hasKidsNames} from 'common/has-kids'
-import {milesToKm} from 'common/measurement-utils'
import {
INVERTED_DIET_CHOICES,
INVERTED_EDUCATION_CHOICES,
@@ -15,6 +11,10 @@ import {
INVERTED_RELIGION_CHOICES,
INVERTED_ROMANTIC_CHOICES,
} from 'common/choices'
+import {FilterFields, initialFilters} from 'common/filters'
+import {hasKidsNames} from 'common/has-kids'
+import {milesToKm} from 'common/measurement-utils'
+import {wantsKidsNames} from 'common/wants-kids'
import {capitalize} from 'lodash'
const filterLabels: Record = {
@@ -81,7 +81,7 @@ export function formatFilters(
location: locationType | null,
choicesIdsToLabels: Record,
measurementSystem?: 'metric' | 'imperial',
- t?: (key: string, fallback: string) => string
+ t?: (key: string, fallback: string) => string,
): string[] | null {
const entries: string[] = []
@@ -130,19 +130,13 @@ export function formatFilters(
// console.log(key, value)
let stringValue = value
if (key === 'has_kids')
- stringValue = translate(
- `profile.has_kids.${value}`,
- hasKidsNames[value as number]
- )
+ stringValue = translate(`profile.has_kids.${value}`, hasKidsNames[value as number])
else if (key === 'wants_kids_strength')
- stringValue = translate(
- `profile.wants_kids_${value}`,
- wantsKidsNames[value as number]
- )
+ stringValue = translate(`profile.wants_kids_${value}`, wantsKidsNames[value as number])
else if (key === 'is_smoker')
stringValue = translate(
`profile.smoker.${value ? 'yes' : 'no'}`,
- value ? 'Smoker' : 'Non-smoker'
+ value ? 'Smoker' : 'Non-smoker',
)
if (Array.isArray(value)) {
if (choicesIdsToLabels[key]) {
@@ -150,51 +144,29 @@ export function formatFilters(
} else if (key === 'mbti') {
value = value.map((s) => INVERTED_MBTI_CHOICES[s])
} else if (key === 'pref_romantic_styles') {
- value = value.map((s) =>
- translate(`profile.romantic.${s}`, INVERTED_ROMANTIC_CHOICES[s])
- )
+ value = value.map((s) => translate(`profile.romantic.${s}`, INVERTED_ROMANTIC_CHOICES[s]))
} else if (key === 'pref_relation_styles') {
value = value.map((s) =>
- translate(
- `profile.relationship.${s}`,
- INVERTED_RELATIONSHIP_CHOICES[s]
- )
+ translate(`profile.relationship.${s}`, INVERTED_RELATIONSHIP_CHOICES[s]),
)
} else if (key === 'relationship_status') {
value = value.map((s) =>
- translate(
- `profile.relationship_status.${s}`,
- INVERTED_RELATIONSHIP_STATUS_CHOICES[s]
- )
+ translate(`profile.relationship_status.${s}`, INVERTED_RELATIONSHIP_STATUS_CHOICES[s]),
)
} else if (key === 'political_beliefs') {
- value = value.map((s) =>
- translate(`profile.political.${s}`, INVERTED_POLITICAL_CHOICES[s])
- )
+ value = value.map((s) => translate(`profile.political.${s}`, INVERTED_POLITICAL_CHOICES[s]))
} else if (key === 'diet') {
- value = value.map((s) =>
- translate(`profile.diet.${s}`, INVERTED_DIET_CHOICES[s])
- )
+ value = value.map((s) => translate(`profile.diet.${s}`, INVERTED_DIET_CHOICES[s]))
} else if (key === 'education_levels') {
- value = value.map((s) =>
- translate(`profile.education.${s}`, INVERTED_EDUCATION_CHOICES[s])
- )
+ value = value.map((s) => translate(`profile.education.${s}`, INVERTED_EDUCATION_CHOICES[s]))
} else if (key === 'religion') {
- value = value.map((s) =>
- translate(`profile.religion.${s}`, INVERTED_RELIGION_CHOICES[s])
- )
+ value = value.map((s) => translate(`profile.religion.${s}`, INVERTED_RELIGION_CHOICES[s]))
} else if (key === 'languages') {
- value = value.map((s) =>
- translate(`profile.language.${s}`, INVERTED_LANGUAGE_CHOICES[s])
- )
+ value = value.map((s) => translate(`profile.language.${s}`, INVERTED_LANGUAGE_CHOICES[s]))
} else if (key === 'pref_gender') {
- value = value.map((s) =>
- translate(`profile.gender.${s}`, INVERTED_GENDERS[s])
- )
+ value = value.map((s) => translate(`profile.gender.${s}`, INVERTED_GENDERS[s]))
} else if (key === 'genders') {
- value = value.map((s) =>
- translate(`profile.gender.${s}`, INVERTED_GENDERS[s])
- )
+ value = value.map((s) => translate(`profile.gender.${s}`, INVERTED_GENDERS[s]))
}
stringValue = value.join(', ')
}
@@ -293,8 +265,7 @@ export function formatFilters(
entries.push(locString)
}
- if (entries.length === 0)
- return [translate('filter.any_new_users', 'Any new user')]
+ if (entries.length === 0) return [translate('filter.any_new_users', 'Any new user')]
return entries
}
diff --git a/common/src/secrets.ts b/common/src/secrets.ts
index dc9af970..4785b7ff 100644
--- a/common/src/secrets.ts
+++ b/common/src/secrets.ts
@@ -1,7 +1,7 @@
import {SecretManagerServiceClient} from '@google-cloud/secret-manager'
+import {refreshConfig} from 'common/envs/prod'
+import {IS_LOCAL} from 'common/hosting/constants'
import {zip} from 'lodash'
-import {refreshConfig} from "common/envs/prod";
-import {IS_LOCAL} from "common/hosting/constants";
// List of secrets that are available to backend (api, functions, scripts, etc.)
// Edit them at:
@@ -57,20 +57,17 @@ export const getSecrets = async (credentials?: any, ...ids: SecretId[]) => {
console.debug('secretIds', secretIds)
const fullSecretNames = secretIds.map(
- (secret: string) =>
- `${client.projectPath(projectId)}/secrets/${secret}/versions/latest`
+ (secret: string) => `${client.projectPath(projectId)}/secrets/${secret}/versions/latest`,
)
const secretResponses = await Promise.all(
fullSecretNames.map((name) =>
client.accessSecretVersion({
name,
- })
- )
- )
- const secretValues = secretResponses.map(([response]) =>
- response.payload!.data!.toString()
+ }),
+ ),
)
+ const secretValues = secretResponses.map(([response]) => response.payload!.data!.toString())
const pairs = zip(secretIds, secretValues) as [string, string][]
return Object.fromEntries(pairs)
}
@@ -87,4 +84,3 @@ export const loadSecretsToEnv = async (credentials?: any) => {
}
refreshConfig()
}
-
diff --git a/common/src/socials.ts b/common/src/socials.ts
index 589feb1b..1173d2d1 100644
--- a/common/src/socials.ts
+++ b/common/src/socials.ts
@@ -1,4 +1,4 @@
-import {discordLink} from "common/constants";
+import {discordLink} from 'common/constants'
export const SITE_ORDER = [
'site', // personal site
@@ -25,12 +25,11 @@ export const SITE_ORDER = [
export type Site = (typeof SITE_ORDER)[number]
// this is a lie, actually people can have anything in their links
-export type Socials = { [key in Site]?: string }
+export type Socials = {[key in Site]?: string}
-export const strip = (site: Site, input: string) =>
- stripper[site]?.(input) ?? input
+export const strip = (site: Site, input: string) => stripper[site]?.(input) ?? input
-const stripper: { [key in Site]: (input: string) => string } = {
+const stripper: {[key in Site]: (input: string) => string} = {
site: (s) => s.replace(/^(https?:\/\/)/, ''),
x: (s) =>
s
@@ -39,13 +38,8 @@ const stripper: { [key in Site]: (input: string) => string } = {
.replace(/\/$/, ''),
discord: (s) => s,
paypal: (s) =>
- s
- .replace(/^(https?:\/\/)?(www\.)?(\w+\.)?paypal\.com\/paypalme\//, '')
- .replace(/\/$/, ''),
- patreon: (s) =>
- s
- .replace(/^(https?:\/\/)?(www\.)?(\w+\.)?patreon\.com\//, '')
- .replace(/\/$/, ''),
+ s.replace(/^(https?:\/\/)?(www\.)?(\w+\.)?paypal\.com\/paypalme\//, '').replace(/\/$/, ''),
+ patreon: (s) => s.replace(/^(https?:\/\/)?(www\.)?(\w+\.)?patreon\.com\//, '').replace(/\/$/, ''),
okcupid: (s) => s.replace(/^(https?:\/\/)/, ''),
calendly: (s) => s,
datingdoc: (s) => s,
@@ -59,9 +53,7 @@ const stripper: { [key in Site]: (input: string) => string } = {
.replace(/\/$/, ''),
mastodon: (s) => s.replace(/^@/, ''),
substack: (s) =>
- s
- .replace(/^(https?:\/\/)?(www\.)?(\w+\.)?substack\.com\//, '')
- .replace(/\/$/, ''),
+ s.replace(/^(https?:\/\/)?(www\.)?(\w+\.)?substack\.com\//, '').replace(/\/$/, ''),
instagram: (s) =>
s
.replace(/^(https?:\/\/)?(www\.)?instagram\.com\//, '')
@@ -73,39 +65,33 @@ const stripper: { [key in Site]: (input: string) => string } = {
.replace(/^@/, '')
.replace(/\/$/, ''),
linkedin: (s) =>
- s
- .replace(/^(https?:\/\/)?(www\.)?linkedin\.com\/(in|company)\//, '')
- .replace(/\/$/, ''),
- facebook: (s) =>
- s.replace(/^(https?:\/\/)?(www\.)?facebook\.com\//, '').replace(/\/$/, ''),
+ s.replace(/^(https?:\/\/)?(www\.)?linkedin\.com\/(in|company)\//, '').replace(/\/$/, ''),
+ facebook: (s) => s.replace(/^(https?:\/\/)?(www\.)?facebook\.com\//, '').replace(/\/$/, ''),
spotify: (s) =>
- s
- .replace(/^(https?:\/\/)?(open\.)?spotify\.com\/(artist|user)\//, '')
- .replace(/\/$/, ''),
+ s.replace(/^(https?:\/\/)?(open\.)?spotify\.com\/(artist|user)\//, '').replace(/\/$/, ''),
}
export const getSocialUrl = (site: Site, handle: string) =>
urler[site]?.(handle) ?? urler.site(handle)
-const urler: { [key in Site]: (handle: string) => string } = {
+const urler: {[key in Site]: (handle: string) => string} = {
site: (s) => (s.startsWith('http') ? s : `https://${s}`),
okcupid: (s) => (s.startsWith('http') ? s : `https://${s}`),
- x: (s) => s.startsWith('http') ? s : `https://x.com/${s}`,
+ x: (s) => (s.startsWith('http') ? s : `https://x.com/${s}`),
discord: (s) =>
(s.length === 17 || s.length === 18) && !isNaN(parseInt(s, 10))
? `https://discord.com/users/${s}` // discord user id
: discordLink, // our server
bluesky: (s) => `https://bsky.app/profile/${s}`,
- mastodon: (s) =>
- s.includes('@') ? `https://${s.split('@')[1]}/@${s.split('@')[0]}` : s,
- substack: (s) => s.startsWith('http') ? s : `https://${s}.substack.com`,
- instagram: (s) => s.startsWith('http') ? s : `https://instagram.com/${s}`,
- github: (s) => s.startsWith('http') ? s : `https://github.com/${s}`,
- linkedin: (s) => s.startsWith('http') ? s : `https://linkedin.com/in/${s}`,
- facebook: (s) => s.startsWith('http') ? s : `https://facebook.com/${s}`,
- spotify: (s) => s.startsWith('http') ? s : `https://open.spotify.com/user/${s}`,
- paypal: (s) => s.startsWith('http') ? s : `https://paypal.com/paypalme/${s}`,
- patreon: (s) => s.startsWith('http') ? s : `https://patreon.com/${s}`,
+ mastodon: (s) => (s.includes('@') ? `https://${s.split('@')[1]}/@${s.split('@')[0]}` : s),
+ substack: (s) => (s.startsWith('http') ? s : `https://${s}.substack.com`),
+ instagram: (s) => (s.startsWith('http') ? s : `https://instagram.com/${s}`),
+ github: (s) => (s.startsWith('http') ? s : `https://github.com/${s}`),
+ linkedin: (s) => (s.startsWith('http') ? s : `https://linkedin.com/in/${s}`),
+ facebook: (s) => (s.startsWith('http') ? s : `https://facebook.com/${s}`),
+ spotify: (s) => (s.startsWith('http') ? s : `https://open.spotify.com/user/${s}`),
+ paypal: (s) => (s.startsWith('http') ? s : `https://paypal.com/paypalme/${s}`),
+ patreon: (s) => (s.startsWith('http') ? s : `https://patreon.com/${s}`),
calendly: (s) => (s.startsWith('http') ? s : `https://${s}`),
datingdoc: (s) => (s.startsWith('http') ? s : `https://${s}`),
friendshipdoc: (s) => (s.startsWith('http') ? s : `https://${s}`),
@@ -113,7 +99,7 @@ const urler: { [key in Site]: (handle: string) => string } = {
connectiondoc: (s) => (s.startsWith('http') ? s : `https://${s}`),
}
-export const PLATFORM_LABELS: { [key in Site]: string } = {
+export const PLATFORM_LABELS: {[key in Site]: string} = {
site: 'Website',
x: 'Twitter/X',
discord: 'Discord',
diff --git a/common/src/supabase/comment.ts b/common/src/supabase/comment.ts
index 2ad73a3d..7d9a0cbd 100644
--- a/common/src/supabase/comment.ts
+++ b/common/src/supabase/comment.ts
@@ -1,6 +1,7 @@
-import { type JSONContent } from '@tiptap/core'
-import { type Row, tsToMillis } from './utils'
-import { type Comment } from 'common/comment'
+import {type JSONContent} from '@tiptap/core'
+import {type Comment} from 'common/comment'
+
+import {type Row, tsToMillis} from './utils'
export const convertComment = (row: Row<'profile_comments'>): Comment => ({
id: row.id + '',
diff --git a/common/src/supabase/schema.ts b/common/src/supabase/schema.ts
index e140c56a..c705d86f 100644
--- a/common/src/supabase/schema.ts
+++ b/common/src/supabase/schema.ts
@@ -1,10 +1,4 @@
-export type Json =
- | string
- | number
- | boolean
- | null
- | { [key: string]: Json | undefined }
- | Json[]
+export type Json = string | number | boolean | null | {[key: string]: Json | undefined} | Json[]
export type Database = {
// Allows to automatically instantiate createClient with right options
@@ -1533,29 +1527,29 @@ export type Database = {
}
Functions: {
calculate_earth_distance_km: {
- Args: { lat1: number; lat2: number; lon1: number; lon2: number }
+ Args: {lat1: number; lat2: number; lon1: number; lon2: number}
Returns: number
}
can_access_private_messages: {
- Args: { channel_id: number; user_id: string }
+ Args: {channel_id: number; user_id: string}
Returns: boolean
}
- firebase_uid: { Args: never; Returns: string }
- get_average_rating: { Args: { user_id: string }; Returns: number }
+ firebase_uid: {Args: never; Returns: string}
+ get_average_rating: {Args: {user_id: string}; Returns: number}
get_compatibility_questions_with_answer_count: {
Args: never
Returns: Record[]
}
get_love_question_answers_and_lovers: {
- Args: { p_question_id: number }
+ Args: {p_question_id: number}
Returns: Record[]
}
get_love_question_answers_and_profiles: {
- Args: { p_question_id: number }
+ Args: {p_question_id: number}
Returns: Record[]
}
get_votes_with_results: {
- Args: { order_by?: string }
+ Args: {order_by?: string}
Returns: {
created_time: string
creator_id: string
@@ -1570,22 +1564,20 @@ export type Database = {
votes_for: number
}[]
}
- is_admin:
- | { Args: never; Returns: boolean }
- | { Args: { user_id: string }; Returns: boolean }
+ is_admin: {Args: never; Returns: boolean} | {Args: {user_id: string}; Returns: boolean}
millis_interval: {
- Args: { end_millis: number; start_millis: number }
+ Args: {end_millis: number; start_millis: number}
Returns: unknown
}
- millis_to_ts: { Args: { millis: number }; Returns: string }
- random_alphanumeric: { Args: { length: number }; Returns: string }
+ millis_to_ts: {Args: {millis: number}; Returns: string}
+ random_alphanumeric: {Args: {length: number}; Returns: string}
rebuild_profile_search: {
- Args: { profile_id_param: number }
+ Args: {profile_id_param: number}
Returns: undefined
}
- show_limit: { Args: never; Returns: number }
- show_trgm: { Args: { '': string }; Returns: string[] }
- to_jsonb: { Args: { '': Json }; Returns: Json }
+ show_limit: {Args: never; Returns: number}
+ show_trgm: {Args: {'': string}; Returns: string[]}
+ to_jsonb: {Args: {'': Json}; Returns: Json}
}
Enums: {
lover_visibility: 'public' | 'member'
@@ -1603,7 +1595,7 @@ type DefaultSchema = DatabaseWithoutInternals[Extract]
export type Tables<
DefaultSchemaTableNameOrOptions extends
| keyof (DefaultSchema['Tables'] & DefaultSchema['Views'])
- | { schema: keyof DatabaseWithoutInternals },
+ | {schema: keyof DatabaseWithoutInternals},
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
@@ -1619,10 +1611,8 @@ export type Tables<
}
? R
: never
- : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema['Tables'] &
- DefaultSchema['Views'])
- ? (DefaultSchema['Tables'] &
- DefaultSchema['Views'])[DefaultSchemaTableNameOrOptions] extends {
+ : DefaultSchemaTableNameOrOptions extends keyof (DefaultSchema['Tables'] & DefaultSchema['Views'])
+ ? (DefaultSchema['Tables'] & DefaultSchema['Views'])[DefaultSchemaTableNameOrOptions] extends {
Row: infer R
}
? R
@@ -1632,7 +1622,7 @@ export type Tables<
export type TablesInsert<
DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema['Tables']
- | { schema: keyof DatabaseWithoutInternals },
+ | {schema: keyof DatabaseWithoutInternals},
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
@@ -1657,7 +1647,7 @@ export type TablesInsert<
export type TablesUpdate<
DefaultSchemaTableNameOrOptions extends
| keyof DefaultSchema['Tables']
- | { schema: keyof DatabaseWithoutInternals },
+ | {schema: keyof DatabaseWithoutInternals},
TableName extends DefaultSchemaTableNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
@@ -1682,7 +1672,7 @@ export type TablesUpdate<
export type Enums<
DefaultSchemaEnumNameOrOptions extends
| keyof DefaultSchema['Enums']
- | { schema: keyof DatabaseWithoutInternals },
+ | {schema: keyof DatabaseWithoutInternals},
EnumName extends DefaultSchemaEnumNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
@@ -1699,7 +1689,7 @@ export type Enums<
export type CompositeTypes<
PublicCompositeTypeNameOrOptions extends
| keyof DefaultSchema['CompositeTypes']
- | { schema: keyof DatabaseWithoutInternals },
+ | {schema: keyof DatabaseWithoutInternals},
CompositeTypeName extends PublicCompositeTypeNameOrOptions extends {
schema: keyof DatabaseWithoutInternals
}
diff --git a/common/src/supabase/users.ts b/common/src/supabase/users.ts
index d9410389..2c79c66c 100644
--- a/common/src/supabase/users.ts
+++ b/common/src/supabase/users.ts
@@ -1,13 +1,9 @@
-import { PrivateUser, User } from 'common/user'
-import { Row, run, SupabaseClient, tsToMillis } from './utils'
+import {PrivateUser, User} from 'common/user'
-export async function getUserForStaticProps(
- db: SupabaseClient,
- username: string
-) {
- const { data } = await run(
- db.from('users').select().ilike('username', username)
- )
+import {Row, run, SupabaseClient, tsToMillis} from './utils'
+
+export async function getUserForStaticProps(db: SupabaseClient, username: string) {
+ const {data} = await run(db.from('users').select().ilike('username', username))
return convertUser(data[0] ?? null)
}
@@ -25,9 +21,7 @@ export function convertUser(row: Row<'users'> | null): User | null {
}
export function convertPrivateUser(row: Row<'private_users'>): PrivateUser
-export function convertPrivateUser(
- row: Row<'private_users'> | null
-): PrivateUser | null {
+export function convertPrivateUser(row: Row<'private_users'> | null): PrivateUser | null {
if (!row) return null
return row.data as PrivateUser
}
diff --git a/common/src/supabase/utils.ts b/common/src/supabase/utils.ts
index 15eeb2de..e0463451 100644
--- a/common/src/supabase/utils.ts
+++ b/common/src/supabase/utils.ts
@@ -6,8 +6,8 @@ import {
SupabaseClientOptions as SupabaseClientOptionsGeneric,
} from '@supabase/supabase-js'
-import {Database} from './schema'
import {User} from '../user'
+import {Database} from './schema'
export type Schema = Database['public']
export type Tables = Schema['Tables']
@@ -27,15 +27,17 @@ export type SupabaseClient = SupabaseClientGeneric
export function createClient(
instanceIdOrUrl: string,
key: string,
- opts?: SupabaseClientOptionsGeneric<'public'>
+ opts?: SupabaseClientOptionsGeneric<'public'>,
) {
// Allow passing a full Supabase URL directly (e.g., http://localhost:54321)
- const url = /:\/\//.test(instanceIdOrUrl) ? instanceIdOrUrl : `https://${instanceIdOrUrl}.supabase.co`
+ const url = /:\/\//.test(instanceIdOrUrl)
+ ? instanceIdOrUrl
+ : `https://${instanceIdOrUrl}.supabase.co`
// console.debug('createClient', instanceId, key, opts)
return createClientGeneric(
url,
key,
- opts
+ opts,
// {
// auth: {
// persistSession: false, // ✅ No localStorage
@@ -51,18 +53,16 @@ export function createClient(
}
export type QueryResponse = PostgrestResponse | PostgrestSingleResponse
-export type QueryMultiSuccessResponse = { data: T[]; count: number }
-export type QuerySingleSuccessResponse = { data: T; count: number }
+export type QueryMultiSuccessResponse = {data: T[]; count: number}
+export type QuerySingleSuccessResponse = {data: T; count: number}
export async function run(
- q: PromiseLike>
+ q: PromiseLike>,
): Promise>
export async function run(
- q: PromiseLike>
+ q: PromiseLike>,
): Promise>
-export async function run