Skip to content

Commit 743c23a

Browse files
authored
Merge pull request #83 from JDIZM/minor-improvements
Minor improvements [JDI-72]
2 parents c478783 + 469eb61 commit 743c23a

23 files changed

+418
-213
lines changed

.env.example

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ PORT=4000
66
## Database
77
###
88
POSTGRES_HOST=db
9+
POSTGRES_PORT=5432
910
POSTGRES_USER=postgres
1011
POSTGRES_PASSWORD=example
1112
POSTGRES_DB=postgres
@@ -15,3 +16,4 @@ POSTGRES_DB=postgres
1516
###
1617
SUPABASE_URL=https://example.supabase.co
1718
SUPABASE_PK=example-key
19+
SUPABASE_AUTH_JWT_SECRET=abcdefghijklmnopqrstuvwxzyz1234567890

Dockerfile

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
1-
FROM node:20-alpine as base
1+
FROM node:20-alpine AS base
22

33
ENV PNPM_VERSION=9.1.0
44

55
RUN npm install -g pnpm@$PNPM_VERSION
66

77
WORKDIR /app
88

9+
ARG NODE_ENV
10+
ENV NODE_ENV=${NODE_ENV}
11+
912
# install deps first so we can cache them
1013
COPY package*.json pnpm-lock.yaml ./
1114
RUN pnpm install --frozen-lockfile

README.md

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -71,11 +71,31 @@ Run the unit tests with `npm run test`
7171

7272
It's also recommended to install the [vitest extension for vscode](https://marketplace.visualstudio.com/items?itemName=ZixuanChen.vitest-explorer).
7373

74+
## Supabase CLI
75+
76+
You can install the supabase cli for local development.
77+
78+
- https://supabase.com/docs/guides/cli/getting-started
79+
- https://supabase.com/docs/guides/cli/local-development
80+
7481
## Database
7582

7683
You can view the database with `npx drizzle-kit studio` or `npm run studio`.
7784

7885
You can spin up a local copy of the database and application with `docker-compose` but this is not required when using the Supabase db.
86+
When using the supabase cli we can run a local copy of the db with `supabase start`.
87+
88+
### Developing locally with supabase
89+
90+
This will provide you with a connection string, you can update the local environment variables in the .env file with the details from the connection string.
91+
92+
`postgresql://postgres:postgres@localhost:54322/postgres`
93+
94+
Visit the Supabase dashboard: http://localhost:54323 and manage your database locally.
95+
96+
### Local Postgres with Docker
97+
98+
You can spin up a local database and application with `docker-compose` but this is not required when using the Supabase db or cli.
7999

80100
```
81101
docker compose up -d
@@ -96,6 +116,7 @@ POSTGRES_HOST=mypostgres
96116
```
97117

98118
Then run the application in docker and connect to the same network.
119+
99120
```bash
100121
docker run --network mynetwork --name node-express -d -p 4000:4000 node-express
101122
```
@@ -171,26 +192,28 @@ How permissions work.
171192

172193
A resource will have a permission level for each route method based on users role within the workspace. Workspace permissions can be defined in `./src/helpers/permissions.ts`.
173194

174-
Workspace permissions:
195+
Workspace level permissions:
175196
Admin: Highest level of access to all resources within the workspace.
176197
User: Regular user with limited permissions.
177198

178-
Resource permissions:
199+
Resource level permissions:
179200
Owner: Has access to their own resources
180201

181-
Account permissions:
202+
Account level permissions:
182203
SuperAdmin: Has access to all super only resources.
183204

184205
### Workspace route permission levels
185206

186-
Ensure every request that requires workspace permissions includes a workspace context. This can be done using the `x-workspace-id header`.
207+
Ensure every request that requires workspace permissions includes a workspace context.
187208

188-
Passing the `x-workspace-id` header will allow the user to access the workspace resources if they are a member of the workspace with a sufficient role.
209+
This can be done by passing the `x-workspace-id` header when making a request.
210+
211+
This will allow the user to access the workspace resources if they are a member of the workspace with a sufficient role.
189212

190213
A role/claim is defined when the account is added to the workspace as a member.
191214

192215
1. User - Can access all resources with user permissions.
193-
2. Admin - Can access all resources.
216+
2. Admin - Can access all resources within the workspace.
194217

195218
## Supabase Auth
196219

@@ -220,5 +243,3 @@ For information on confguring the app level environment variables see [How to us
220243
- `POSTGRES_DB`: `postgres`
221244
- `SUPABASE_URL`: `https://<supabase-id>.supabase.co`
222245
- `SUPABASE_PK`: `abcdefghijklm`
223-
224-

docker-compose.yml

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,3 @@
1-
# Use postgres/example user/password credentials
2-
version: "3.1"
3-
41
services:
52
app:
63
build:
@@ -9,16 +6,16 @@ services:
96
env_file:
107
- .env
118
ports:
12-
- "4000:4000"
9+
- ${PORT}:${PORT}
1310
db:
14-
image: postgres
11+
image: postgres:15
1512
restart: always
1613
environment:
1714
POSTGRES_USER: ${POSTGRES_USER}
1815
POSTGRES_DB: ${POSTGRES_DB}
1916
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
2017
ports:
21-
- 5432:5432
18+
- ${POSTGRES_PORT:-5432}:${POSTGRES_PORT:-5432}
2219
volumes:
2320
- db:/var/lib/postgresql/data
2421
volumes:

drizzle.config.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@ export default {
1212
dbCredentials: {
1313
host: config.db_host,
1414
user: config.db_user,
15-
port: 5432,
15+
port: config.db_port,
1616
password: config.db_password,
1717
database: config.db_name,
18-
ssl: config.env !== "dev"
18+
ssl: config.env !== "development"
1919
}
2020
} satisfies Config;

package.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
"author": "",
2929
"license": "ISC",
3030
"devDependencies": {
31+
"@types/jsonwebtoken": "^9.0.6",
3132
"@typescript-eslint/eslint-plugin": "^8.0.0",
3233
"@typescript-eslint/parser": "^8.0.0",
3334
"@vitest/coverage-v8": "^2.0.0",
@@ -68,6 +69,7 @@
6869
"drizzle-zod": "^0.5.1",
6970
"express": "^4.19.2",
7071
"helmet": "^7.1.0",
72+
"jsonwebtoken": "^9.0.2",
7173
"pg": "^8.11.5",
7274
"pino": "^9.1.0",
7375
"pino-http": "^10.1.0"

src/config.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,36 @@
11
import dotenv from "dotenv";
22
import { logger } from "@/helpers/index.ts";
33

4-
export const stages = ["dev", "prod"] as const;
5-
6-
export const ENV = (process.env.NODE_ENV as Stage) ?? ("dev" as Stage);
4+
export const stages = ["development", "production", "test"] as const;
75

86
export type Stage = (typeof stages)[number];
97

10-
export const getStage = () => {
11-
if (!stages.includes(ENV)) {
12-
logger.error(`Invalid environment: ${ENV}`);
8+
export const ENV = process.env.NODE_ENV ?? "development";
9+
10+
export const getStage = (env: string): Stage => {
11+
if (!stages.includes(env as Stage)) {
1312
throw new Error(`Invalid environment: ${ENV}`);
1413
}
15-
return ENV;
14+
return env as Stage;
1615
};
1716

18-
export const STAGE = getStage();
17+
export const STAGE = getStage(ENV);
1918

2019
logger.info(`running in env: ${STAGE}`);
2120

22-
if (STAGE === "dev") {
21+
if (STAGE !== "production") {
2322
dotenv.config();
2423
}
2524

2625
export const config = {
2726
env: STAGE,
28-
port: process.env.PORT || 3000,
27+
port: process.env.PORT || 4000,
2928
db_host: process.env.POSTGRES_HOST || "localhost",
29+
db_port: Number(process.env.POSTGRES_PORT) || 5432,
3030
db_user: process.env.POSTGRES_USER || "postgres",
3131
db_password: process.env.POSTGRES_PASSWORD || "postgres",
3232
db_name: process.env.POSTGRES_DB || "test",
33-
appUrl: process.env.APP_URL || "http://localhost:3000",
3433
supabaseUrl: process.env.SUPABASE_URL || "https://example.supabase.co",
35-
supabaseKey: process.env.SUPABASE_PK || "example-key"
34+
supabaseKey: process.env.SUPABASE_PK || "example-key",
35+
jwtSecret: process.env.SUPABASE_AUTH_JWT_SECRET || "super-secret-key-that-should-be-replaced"
3636
};

src/cors.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
export const whitelist: RegExp[] = [
22
/^https?:\/\/localhost:3000$/,
3-
/^https?:\/\/localhost:4000$/,
43
/^https?:\/\/example\.com$/,
54
/^https?:\/\/subdomain\.example\.com$/
65
// Add more patterns as needed

src/handlers/accounts/accounts.handlers.ts

Lines changed: 3 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,7 @@ export async function getAccounts(req: Request, res: Response) {
1616

1717
return res.status(response.code).send(response);
1818
} catch (err) {
19-
const error = err as Error;
20-
21-
const message = "Unable to fetch accounts";
22-
23-
logger.error({ msg: message, err });
24-
25-
const response = gatewayResponse().error(400, error, error.message);
19+
const response = gatewayResponse().error(400, err as Error, "Unable to fetch accounts");
2620

2721
res.status(response.code).send(response);
2822
}
@@ -58,13 +52,7 @@ export async function getAccount(req: Request, res: Response) {
5852

5953
return res.status(response.code).send(response);
6054
} catch (err) {
61-
const error = err as Error;
62-
63-
const message = "Unable to fetch account";
64-
65-
logger.error({ msg: message, err });
66-
67-
const response = gatewayResponse().error(400, error, error.message);
55+
const response = gatewayResponse().error(400, err as Error, "Unable to fetch account");
6856

6957
return res.status(response.code).send(response);
7058
}
@@ -82,13 +70,7 @@ export async function createAccount(req: Request, res: Response) {
8270

8371
return res.status(response.code).send(response);
8472
} catch (err) {
85-
const error = err as Error;
86-
87-
const message = "Unable to create account";
88-
89-
logger.error({ msg: message, err: error });
90-
91-
const response = gatewayResponse().error(400, error, error.message);
73+
const response = gatewayResponse().error(400, err as Error, "Unable to create account");
9274

9375
return res.status(response.code).send(response);
9476
}

src/handlers/auth/auth.handlers.ts

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -24,19 +24,18 @@ export const signUp = async (req: Request, res: Response) => {
2424
const user = await signUpWithSupabase(email, password);
2525

2626
if (!user) {
27-
logger.error({ msg: "Unable to sign up" });
27+
const errorMessage = "Unable to sign up";
2828

29-
const response = gatewayResponse().error(400, new Error("Unable to sign up"), "Unable to sign up");
29+
const response = gatewayResponse().error(400, new Error(errorMessage), errorMessage);
3030

3131
return res.status(response.code).send(response);
3232
}
3333

34-
logger.info("User signed up", 200, user.id);
34+
logger.info({ msg: `User signed up with id: ${user.id}` });
3535

3636
const dbAccount = await createDbAccount({ email, fullName, phone, uuid: user.id });
37-
logger.info({ msg: `Account created in DB with id: ${dbAccount}` });
3837

39-
const response = gatewayResponse().success(200, user);
38+
const response = gatewayResponse().success(200, `Account created in DB with id: ${dbAccount}`);
4039

4140
return res.status(response.code).send(response);
4241
} catch (err) {
@@ -52,16 +51,17 @@ export const signUp = async (req: Request, res: Response) => {
5251
}
5352
};
5453

54+
// TODO add httpOnly cookie for sessions.. https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#security
5555
export const signInWithPassword = async (req: Request, res: Response) => {
5656
const { email, password } = req.body;
57+
5758
const { data, error } = await supabase.auth.signInWithPassword({
5859
email,
5960
password
6061
});
6162

6263
if (error) {
6364
const response = gatewayResponse().error(400, error, "Unable to sign in with password");
64-
logger.error("Unable to sign in with password", error);
6565

6666
return res.status(response.code).send(response);
6767
}

src/handlers/auth/auth.methods.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
import jwt from "jsonwebtoken";
2+
import { config } from "../../config.ts";
3+
import { logger } from "@/helpers/index.ts";
4+
5+
export const verifyToken = async (token: string) => {
6+
try {
7+
return jwt.verify(token, config.jwtSecret) as { sub: string };
8+
} catch (err) {
9+
logger.error("Token validation failed:", err);
10+
return null;
11+
}
12+
};

src/handlers/memberships/memberships.handlers.ts

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -72,13 +72,7 @@ export async function createMembershipHandler(req: Request, res: Response) {
7272

7373
return res.status(response.code).send(response);
7474
} catch (err) {
75-
const error = err as Error;
76-
77-
const message = "Error creating membership";
78-
79-
logger.error({ msg: message, err: error });
80-
81-
const response = gatewayResponse().error(400, error, message);
75+
const response = gatewayResponse().error(400, err as Error, "Error creating membership");
8276

8377
return res.status(response.code).send(response);
8478
}

0 commit comments

Comments
 (0)