Skip to content

Commit f7054ac

Browse files
Make contribution more accessible.
1 parent c1bc360 commit f7054ac

5 files changed

Lines changed: 165 additions & 34 deletions

File tree

.env.example

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
VITE_SERVER_URL=http://localhost:9901 # Make sure this matches the PORT value below (in development)
22
FRONTEND_URL=http://localhost:5173 # Make sure this matches the vite.config.ts
33

4-
POSTGRES_DB_URL=postgresql://
5-
POSTGRES_DB_URL_FLIGHTS=postgresql://
6-
POSTGRES_DB_URL_CHATS=postgresql://
4+
POSTGRES_DB_URL=postgresql://pfcontrol:YOUR_POSTGRES_PASSWORD_HERE@localhost:5432/pfcontrol
5+
POSTGRES_DB_URL_FLIGHTS=postgresql://pfcontrol:YOUR_POSTGRES_PASSWORD_HERE@localhost:5432/pfcontrol_flights
6+
POSTGRES_DB_URL_CHATS=postgresql://pfcontrol:YOUR_POSTGRES_PASSWORD_HERE@localhost:5432/pfcontrol_chats
77
POSTGRES_DB_PW=YOUR_POSTGRES_PASSWORD_HERE
8-
DB_ENCRYPTION_KEY=YOUR_ENCRYPTION_KEY_HERE # Must be 128 Characters Long
9-
REDIS_URL=redis://
8+
DB_ENCRYPTION_KEY=YOUR_ENCRYPTION_KEY_HERE # Must be 128 Characters Long - use this in terminal: node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"
9+
REDIS_URL=redis://localhost:6379
1010

1111
DISCORD_CLIENT_ID=YOUR_CLIENT_ID_HERE # Get it from https://discord.com/developers/applications
1212
DISCORD_CLIENT_SECRET=YOUR_CLIENT_SECRET_HERE # Get it from https://discord.com/developers/applications

README.md

Lines changed: 41 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,27 +13,56 @@ If you just want to try or demo PFControl:
1313

1414
## Development — Local setup
1515

16-
The following steps get the project running on your machine for development and testing.
16+
Getting started with PFControl development is straightforward. We've set up Docker Compose to handle PostgreSQL and Redis for you, so you don't need to manually configure databases.
1717

18-
1. Install dependencies
18+
### Setup instructions
1919

20-
```
21-
npm install
22-
```
20+
1. **Install Docker Desktop** (if you don't have it)
21+
Download from [docker.com](https://www.docker.com/products/docker-desktop) and open it. Make sure it fully starts up.
2322

24-
2. Create an environment file
25-
Copy the example and update environment variables into `.env.development`.
23+
2. **Clone the repository**
24+
```bash
25+
git clone https://github.com/PFConnect/pfcontrol-2.git
26+
cd pfcontrol-2
27+
```
2628

27-
> **Note:** For full functionality, you must set up PostgreSQL and Redis and provide the correct connection URLs in your `.env.development` file.
28-
> If you are unable to set up these services locally, you can still run the frontend, but backend features will be limited or unavailable.
29-
> If you need help or require development environment variables, join our [Discord server](https://pfconnect.online/discord), create a ticket, and ask for assistance.
29+
3. **Start PostgreSQL and Redis**
30+
```bash
31+
docker-compose -f docker-compose.dev.yml up -d
32+
```
33+
This starts local PostgreSQL and Redis containers in the background. First-time setup downloads the images and takes about 30 seconds.
3034

31-
3. Start the development environment
35+
4. **Set up your environment file**
36+
```bash
37+
cp .env.example .env.development
3238
```
39+
This creates a `.env.development` file with localhost connection strings that point to the Docker containers.
40+
41+
5. **Install dependencies and start the dev server**
42+
```bash
43+
npm install
3344
npm run dev
3445
```
3546

36-
Frontend will be available at http://localhost:5173 and Backend API at http://localhost:9901 by default.
47+
That's it! The frontend will be at [http://localhost:5173](http://localhost:5173) and the backend API at [http://localhost:9901](http://localhost:9901).
48+
49+
**When you're done**, stop the databases with:
50+
```bash
51+
docker-compose -f docker-compose.dev.yml down
52+
```
53+
54+
To reset your local database (fresh start), run:
55+
```bash
56+
docker-compose -f docker-compose.dev.yml down -v
57+
docker-compose -f docker-compose.dev.yml up -d
58+
```
59+
60+
### Troubleshooting
61+
62+
- **"Cannot connect to the Docker daemon"**: Make sure Docker Desktop is running. Open the Docker Desktop application before running docker-compose commands
63+
- **"Cannot connect to PostgreSQL"**: Make sure Docker Compose is running (`docker ps` should show postgres and redis containers)
64+
- **"Port 5432 already in use"**: You might have PostgreSQL installed locally. Either stop your local PostgreSQL or change the port mapping in `docker-compose.dev.yml`
65+
- **Need help?** Join our [Discord server](https://pfconnect.online/discord), create a ticket, and we'll help you out
3766

3867
## Project structure
3968

docker-compose.dev.yml

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,42 @@
11
version: '3.8'
22

33
services:
4+
postgres:
5+
image: postgres:16-alpine
6+
container_name: pfcontrol-postgres-dev
7+
ports:
8+
- '5432:5432'
9+
environment:
10+
POSTGRES_USER: pfcontrol
11+
POSTGRES_PASSWORD: pfcontrol
12+
POSTGRES_DB: pfcontrol
13+
volumes:
14+
- postgres_data:/var/lib/postgresql/data
15+
restart: unless-stopped
16+
networks:
17+
- pfcontrol-network
18+
healthcheck:
19+
test: ['CMD-SHELL', 'pg_isready -U pfcontrol']
20+
interval: 10s
21+
timeout: 5s
22+
retries: 5
23+
24+
redis:
25+
image: redis:7-alpine
26+
container_name: pfcontrol-redis-dev
27+
ports:
28+
- '6379:6379'
29+
volumes:
30+
- redis_data:/data
31+
restart: unless-stopped
32+
networks:
33+
- pfcontrol-network
34+
healthcheck:
35+
test: ['CMD', 'redis-cli', 'ping']
36+
interval: 10s
37+
timeout: 3s
38+
retries: 5
39+
440
pfcontrol-dev:
541
build:
642
context: .
@@ -18,6 +54,15 @@ services:
1854
restart: unless-stopped
1955
networks:
2056
- pfcontrol-network
57+
depends_on:
58+
postgres:
59+
condition: service_healthy
60+
redis:
61+
condition: service_healthy
62+
63+
volumes:
64+
postgres_data:
65+
redis_data:
2166

2267
networks:
2368
pfcontrol-network:

server/db/connection.ts

Lines changed: 65 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,66 @@ import type { MainDatabase } from './types/connection/MainDatabase';
1515
import type { FlightsDatabase } from './types/connection/FlightsDatabase';
1616
import type { ChatsDatabase } from './types/connection/ChatsDatabase';
1717

18+
// create databases if they don't exist
19+
async function ensureDatabasesExist() {
20+
const mainDbUrl = process.env.POSTGRES_DB_URL;
21+
if (!mainDbUrl) {
22+
throw new Error('POSTGRES_DB_URL is not defined');
23+
}
24+
25+
const url = new URL(mainDbUrl);
26+
const baseConnectionString = `postgresql://${url.username}:${url.password}@${url.host}/postgres`;
27+
28+
const isLocalhost = url.hostname === 'localhost' || url.hostname === '127.0.0.1';
29+
30+
const client = new pg.Client({
31+
connectionString: baseConnectionString,
32+
ssl: isLocalhost ? false : { rejectUnauthorized: false },
33+
});
34+
35+
try {
36+
await client.connect();
37+
38+
const flightsDbName = 'pfcontrol_flights';
39+
const flightsResult = await client.query(
40+
`SELECT 1 FROM pg_database WHERE datname = $1`,
41+
[flightsDbName]
42+
);
43+
if (flightsResult.rows.length === 0) {
44+
await client.query(`CREATE DATABASE ${flightsDbName}`);
45+
console.log(`[Database] Created database: ${flightsDbName}`);
46+
}
47+
48+
const chatsDbName = 'pfcontrol_chats';
49+
const chatsResult = await client.query(
50+
`SELECT 1 FROM pg_database WHERE datname = $1`,
51+
[chatsDbName]
52+
);
53+
if (chatsResult.rows.length === 0) {
54+
await client.query(`CREATE DATABASE ${chatsDbName}`);
55+
console.log(`[Database] Created database: ${chatsDbName}`);
56+
}
57+
} catch (error) {
58+
console.error('[Database] Error ensuring databases exist:', error);
59+
throw error;
60+
} finally {
61+
await client.end();
62+
}
63+
}
64+
65+
await ensureDatabasesExist();
66+
67+
function getSSLConfig(connectionString: string) {
68+
const url = new URL(connectionString);
69+
const isLocalhost = url.hostname === 'localhost' || url.hostname === '127.0.0.1';
70+
return isLocalhost ? false : { rejectUnauthorized: false };
71+
}
72+
1873
export const mainDb = new Kysely<MainDatabase>({
1974
dialect: new PostgresDialect({
2075
pool: new pg.Pool({
2176
connectionString: process.env.POSTGRES_DB_URL,
22-
ssl: { rejectUnauthorized: false },
77+
ssl: getSSLConfig(process.env.POSTGRES_DB_URL as string),
2378
}),
2479
}),
2580
});
@@ -28,7 +83,7 @@ export const flightsDb = new Kysely<FlightsDatabase>({
2883
dialect: new PostgresDialect({
2984
pool: new pg.Pool({
3085
connectionString: process.env.POSTGRES_DB_URL_FLIGHTS,
31-
ssl: { rejectUnauthorized: false },
86+
ssl: getSSLConfig(process.env.POSTGRES_DB_URL_FLIGHTS as string),
3287
}),
3388
}),
3489
});
@@ -37,7 +92,7 @@ export const chatsDb = new Kysely<ChatsDatabase>({
3792
dialect: new PostgresDialect({
3893
pool: new pg.Pool({
3994
connectionString: process.env.POSTGRES_DB_URL_CHATS,
40-
ssl: { rejectUnauthorized: false },
95+
ssl: getSSLConfig(process.env.POSTGRES_DB_URL_CHATS as string),
4196
}),
4297
}),
4398
});
@@ -55,12 +110,11 @@ redisConnection.on('connect', () => {
55110
console.log('[Redis] Connected successfully');
56111
});
57112

58-
createMainTables().catch((err) => {
59-
console.error('Failed to create main tables:', err);
60-
process.exit(1);
61-
});
62-
63-
createGlobalChatTable().catch((err) => {
64-
console.error('Failed to create global chat table:', err);
113+
try {
114+
await createMainTables();
115+
await createGlobalChatTable();
116+
console.log('[Database] Tables initialized successfully');
117+
} catch (err) {
118+
console.error('Failed to create tables:', err);
65119
process.exit(1);
66-
});
120+
}

server/db/schemas.ts

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ export async function createMainTables() {
55
await mainDb.schema
66
.createTable('app_settings')
77
.ifNotExists()
8-
.addColumn('key', 'varchar(255)', (col) => col.primaryKey())
9-
.addColumn('value', 'text')
8+
.addColumn('id', 'integer', (col) => col.primaryKey())
9+
.addColumn('version', 'varchar(50)', (col) => col.notNull())
10+
.addColumn('updated_at', 'timestamp', (col) => col.notNull())
11+
.addColumn('updated_by', 'varchar(255)', (col) => col.notNull())
1012
.execute();
1113

1214
// users (assuming based on typical UsersTable interface)
@@ -27,6 +29,7 @@ export async function createMainTables() {
2729
.createTable('sessions')
2830
.ifNotExists()
2931
.addColumn('id', 'varchar(255)', (col) => col.primaryKey())
32+
.addColumn('session_id', 'varchar(255)', (col) => col.notNull())
3033
.addColumn('user_id', 'varchar(255)', (col) =>
3134
col.references('users.id').onDelete('cascade')
3235
)
@@ -89,6 +92,7 @@ export async function createMainTables() {
8992
.addColumn('id', 'serial', (col) => col.primaryKey())
9093
.addColumn('title', 'varchar(255)', (col) => col.notNull())
9194
.addColumn('message', 'text', (col) => col.notNull())
95+
.addColumn('show', 'boolean', (col) => col.defaultTo(true))
9296
.addColumn('created_at', 'timestamp', (col) => col.defaultTo('now()'))
9397
.execute();
9498

@@ -122,10 +126,9 @@ export async function createMainTables() {
122126
await mainDb.schema
123127
.createTable('tester_settings')
124128
.ifNotExists()
125-
.addColumn('tester_id', 'integer', (col) =>
126-
col.references('testers.id').onDelete('cascade').primaryKey()
127-
)
128-
.addColumn('settings', 'jsonb')
129+
.addColumn('id', 'serial', (col) => col.primaryKey())
130+
.addColumn('setting_key', 'varchar(255)', (col) => col.unique().notNull())
131+
.addColumn('setting_value', 'boolean', (col) => col.notNull())
129132
.execute();
130133

131134
// daily_statistics

0 commit comments

Comments
 (0)