21 Commits

Author SHA1 Message Date
20adae3675 Delete .github/workflows/deploy.yml
Some checks failed
Deploy to Coolify / deploy (push) Has been cancelled
2026-09-02 21:21:42 +00:00
cd7d529b86 Rename the workflow
Some checks failed
Deploy to Coolify / deploy (push) Has been cancelled
2026-09-02 21:21:26 +00:00
15504ecba3 Add the build step to the Dockerfile
All checks were successful
Publish image / deploy (push) Successful in 3m47s
2026-09-02 13:49:27 -07:00
e98ef96b2e Update package.json so start no longer runs build
Some checks failed
Publish image / deploy (push) Has been cancelled
2026-09-02 20:40:40 +00:00
98eb1083eb Add image deployment workflow
Some checks failed
Publish image / deploy (push) Has been cancelled
2026-09-01 13:58:08 +00:00
3aa2a2583d Merge pull request #110 from brendan-ch/claude/apollo-router-migration-research-y8oVn
claude/apollo-router-migration-research-y8oVn
2026-03-27 18:35:47 -07:00
5b441b3698 Fix remaining npm audit issues 2026-03-27 18:15:33 -07:00
e45a94f260 Upgrade Apollo Server 4 to 5
@apollo/server v4 reached end-of-life on January 26, 2026. This bumps
to v5 (resolves to 5.5.0) and raises the graphql peer requirement to
^16.11.0 as required by AS5. No application code changes were needed
since the project already used the AS5-compatible patterns (separate
@as-integrations/express5 package, context in expressMiddleware options).

https://claude.ai/code/session_015DcGUmHQtHQRt2QUSefXga
2026-03-27 15:36:15 +00:00
c088f1003b Merge pull request #109 from brendan-ch/chore/remove-docker-host-in-deploy
chore/remove-docker-host-in-deploy
2026-03-24 16:13:42 -07:00
e0e4092218 Add Docker host and runtime directory exports 2026-03-24 16:12:28 -07:00
893274ca6a Remove DOCKER_HOST export in deploy.yml
Remove Docker host export from deployment script.
2026-03-24 16:08:12 -07:00
0601d9e6a0 Merge pull request #108 from brendan-ch/chore/remove-tailscale-for-deploy
chore/remove-tailscale-for-deploy
2026-03-24 16:01:33 -07:00
91f5f4ba7c Remove Tailscale connection from deploy workflow
Removed Tailscale connection step from deployment workflow.
2026-03-24 15:57:38 -07:00
4764ee6af0 Merge pull request #106 from brendan-ch/claude/add-initial-region-loading-jfNcI 2026-03-23 15:23:47 -07:00
44d2da45d1 Update Chapman region to wider campus bounds
https://claude.ai/code/session_0162emnCi1KxW5FkTNn2ZrvH
2026-03-23 22:08:57 +00:00
fe649c81eb Change region to use top-left and bottom-right coordinates
Replace center+delta region representation with a bounding box defined
by topLeft and bottomRight coordinate pairs, which maps more directly
to the visible map area.

https://claude.ai/code/session_0162emnCi1KxW5FkTNn2ZrvH
2026-03-23 22:05:57 +00:00
946f8dd342 Add initial region loading for supported systems
Expose an initialRegion field on the System GraphQL type so the app can
display the correct campus area on first load instead of a zoomed-out
default map view. Chapman University is configured with coordinates
centered on campus.

https://claude.ai/code/session_0162emnCi1KxW5FkTNn2ZrvH
2026-03-23 22:02:42 +00:00
690b2e1559 Merge pull request #105 from brendan-ch:chore/update-caddy-config
chore/update-caddy-config
2026-02-25 16:42:23 -08:00
78f20fa925 Remove the Caddyfile and expose port 4000 locally 2026-02-25 16:40:43 -08:00
90d993736d Merge pull request #104 from brendan-ch/chore/patch-deploy-workflow-1
chore/patch-deploy-workflow
2026-02-23 12:40:31 -08:00
4d79b842f5 Set Docker host for deployment workflow 2026-02-23 12:39:59 -08:00
14 changed files with 450 additions and 872 deletions

47
.github/workflows/deploy-coolify.yml vendored Normal file
View File

@@ -0,0 +1,47 @@
name: "Deploy to Coolify"
on:
push:
branches:
- main
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v3
with:
submodules: recursive
- name: Login to Gitea Container Registry
uses: docker/login-action@v4
with:
registry: gitea.bchen.dev
username: ${{ github.actor }}
password: ${{ secrets.PAT }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v4
- name: Build and push
uses: docker/build-push-action@v7
with:
context: .
push: true
tags: gitea.bchen.dev/${{ github.actor }}/project-inter-server:latest
- name: Set up Tailscale
uses: tailscale/github-action@v4
with:
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.TS_OAUTH_SECRET }}
tags: tag:ci
use-cache: false
tailscaled-args: --tun=userspace-networking --socks5-server=localhost:1055 --outbound-http-proxy-listen=localhost:1055
- name: Deploy to Coolify
run: |
curl --request POST '${{ secrets.COOLIFY_WEBHOOK }}' --header 'Authorization: Bearer ${{ secrets.COOLIFY_TOKEN }}'

View File

@@ -1,59 +0,0 @@
name: "Deploy to birb co."
on:
push:
branches:
- main
workflow_dispatch:
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Check out repository
uses: actions/checkout@v3
# See documentation: https://github.com/tailscale/github-action?tab=readme-ov-file
- name: Connect to Tailscale
uses: tailscale/github-action@v4
with:
oauth-client-id: ${{ secrets.TS_OAUTH_CLIENT_ID }}
oauth-secret: ${{ secrets.TS_OAUTH_CLIENT_SECRET }}
tags: tag:ci
- name: Set up SSH key
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan ${{ vars.HOST }} >> ~/.ssh/known_hosts
- name: Remove directory from server
run: |
ssh -i ~/.ssh/id_ed25519 ${{ vars.USERNAME }}@${{ vars.HOST }} << 'EOF'
rm -rf ~/${{ vars.DIRECTORY_NAME }}
EOF
# Avoid needing to set up SSH access to GitHub for this user
- name: Transfer repository files to server
run: |
scp -i ~/.ssh/id_ed25519 -r ./* ${{ vars.USERNAME }}@${{ vars.HOST }}:~/${{ vars.DIRECTORY_NAME }}
- name: Deploy on server with Docker
run: |
ssh -i ~/.ssh/id_ed25519 ${{ vars.USERNAME }}@${{ vars.HOST }} << 'EOF'
cd ~/${{ vars.DIRECTORY_NAME }}
export APNS_IS_PRODUCTION=${{ secrets.APNS_IS_PRODUCTION }}
export APNS_BUNDLE_ID=${{ secrets.APNS_BUNDLE_ID }}
export APNS_TEAM_ID=${{ secrets.APNS_TEAM_ID }}
export APNS_KEY_ID=${{ secrets.APNS_KEY_ID }}
export APNS_PRIVATE_KEY=${{ secrets.APNS_PRIVATE_KEY }}
export PARKING_LOGGING_INTERVAL_MS=${{ secrets.PARKING_LOGGING_INTERVAL_MS }}
export RATE_LIMITS_DISABLED=${{ secrets.RATE_LIMITS_DISABLED }}
export RATE_LIMIT_WINDOW_MS=${{ secrets.RATE_LIMIT_WINDOW_MS }}
export RATE_LIMIT_DELAY_AFTER_REQUESTS=${{ secrets.RATE_LIMIT_DELAY_AFTER_REQUESTS }}
export RATE_LIMIT_DELAY_MULTIPLIER_MS=${{ secrets.RATE_LIMIT_DELAY_MULTIPLIER_MS }}
docker compose -f docker-compose.prod.yml down --remove-orphans
docker compose -f docker-compose.prod.yml up -d --build
EOF

View File

@@ -1,4 +0,0 @@
interchange-api.bchen.dev {
reverse_proxy app:4000
}

View File

@@ -1,4 +1,7 @@
FROM node:22-alpine
WORKDIR /usr/src/app
COPY . .
RUN npm run build
EXPOSE 4000
CMD ["npm", "run", "start"]

View File

@@ -19,6 +19,8 @@ services:
build: .
command: npm run start
restart: unless-stopped
ports:
- "4000:4000"
depends_on:
- redis
environment:
@@ -34,18 +36,6 @@ services:
- ./redis-stack.conf:/redis-stack.conf
command: redis-stack-server /redis-stack.conf
caddy:
image: caddy:2-alpine
restart: unless-stopped
ports:
- "80:80"
- "443:443"
- "443:443/udp"
volumes:
- ./Caddyfile:/etc/caddy/Caddyfile
- caddy_data:/data
- caddy_config:/config
volumes:
redis_data:
caddy_data:

1121
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -7,7 +7,7 @@
"build:dev": "npm install --include=dev && npm run generate && tsc --project tsconfig.build.json",
"build": "npm install --include=dev && npm run generate && tsc --project tsconfig.build.json && npm prune --omit=dev",
"start:dev": "npm run build:dev && node ./dist/index.js",
"start": "npm run build && node ./dist/index.js",
"start": "node ./dist/index.js",
"generate": "graphql-codegen --config codegen.ts",
"test": "npm run build:dev && jest --runInBand"
},
@@ -26,12 +26,12 @@
},
"private": true,
"dependencies": {
"@apollo/server": "^4.11.2",
"@apollo/server": "^5.0.0",
"@as-integrations/express5": "^1.1.2",
"express": "^5.1.0",
"express-rate-limit": "^8.0.1",
"express-slow-down": "^3.0.0",
"graphql": "^16.10.0",
"graphql": "^16.11.0",
"jsonwebtoken": "^9.0.2",
"redis": "^4.7.0"
},

View File

@@ -6,6 +6,7 @@ scalar DateTime
type System {
id: ID!
name: String!
initialRegion: Region
routes: [Route!]
route(id: ID): Route
stops: [Stop!]
@@ -16,6 +17,11 @@ type System {
parkingSystem: ParkingSystem
}
type Region {
topLeft: Coordinates!
bottomRight: Coordinates!
}
type ParkingSystem {
systemId: ID!
parkingStructures: [ParkingStructure!]

View File

@@ -19,6 +19,7 @@ import { InMemoryExternalSourceETARepository } from "../repositories/shuttle/eta
import { ETAGetterRepository } from "../repositories/shuttle/eta/ETAGetterRepository";
import { InMemorySelfUpdatingETARepository } from "../repositories/shuttle/eta/InMemorySelfUpdatingETARepository";
import { BaseInMemoryETARepository } from "../repositories/shuttle/eta/BaseInMemoryETARepository";
import { IRegion } from "./SharedEntities";
export interface InterchangeSystemBuilderArguments {
name: string;
@@ -49,6 +50,12 @@ export interface InterchangeSystemBuilderArguments {
* at a stop, in latitude/longitude degrees.
*/
shuttleStopArrivalDegreeDelta: number;
/**
* The initial map region to display when the app first loads
* this system. Represents a center coordinate and span.
*/
initialRegion?: IRegion;
}
export class InterchangeSystem {
@@ -62,6 +69,7 @@ export class InterchangeSystem {
public notificationRepository: NotificationRepository,
public parkingTimedDataLoader: TimedApiBasedRepositoryLoader | null,
public parkingRepository: ParkingGetterSetterRepository | null,
public initialRegion: IRegion | null,
) {
}
@@ -96,6 +104,7 @@ export class InterchangeSystem {
notificationRepository,
timedParkingLoader,
parkingRepository,
args.initialRegion ?? null,
);
}
@@ -205,6 +214,7 @@ export class InterchangeSystem {
notificationRepository,
timedParkingLoader,
parkingRepository,
args.initialRegion ?? null,
);
}

View File

@@ -11,3 +11,8 @@ export interface ICoordinates {
longitude: number;
}
export interface IRegion {
topLeft: ICoordinates;
bottomRight: ICoordinates;
}

View File

@@ -25,6 +25,10 @@ const supportedSystems: InterchangeSystemBuilderArguments[] = [
name: "Chapman University",
useSelfUpdatingEtas: true,
shuttleStopArrivalDegreeDelta: 0.001,
initialRegion: {
topLeft: { latitude: 33.85733, longitude: -117.89553 },
bottomRight: { latitude: 33.73970, longitude: -117.81878 },
},
}
]

View File

@@ -83,6 +83,14 @@ export const SystemResolvers: Resolvers<ServerContext> = {
const shuttles = await system.shuttleRepository.getShuttles();
return shuttles.slice().sort((a, b) => a.name.localeCompare(b.name));
},
initialRegion: async (parent, _args, contextValue, _info) => {
const system = contextValue.findSystemById(parent.id);
if (!system) {
return null;
}
return system.initialRegion;
},
parkingSystem: async (parent, _args, contextValue, _info) => {
const system = contextValue.findSystemById(parent.id);
if (!system) {

View File

@@ -35,6 +35,37 @@ describe("SystemResolvers", () => {
});
}
describe("initialRegion", () => {
const query = `
query GetSystemInitialRegion($systemId: ID!) {
system(id: $systemId) {
initialRegion {
topLeft {
latitude
longitude
}
bottomRight {
latitude
longitude
}
}
}
}
`;
it("returns the initial region for the system", async () => {
const response = await getResponseFromQueryNeedingSystemId(query);
assert(response.body.kind === "single");
expect(response.body.singleResult.errors).toBeUndefined();
const initialRegion = (response.body.singleResult.data as any).system.initialRegion;
expect(initialRegion).toEqual({
topLeft: { latitude: 33.85733, longitude: -117.89553 },
bottomRight: { latitude: 33.7397, longitude: -117.81878 },
});
});
});
describe("routes", () => {
const query = `
query GetSystemRoutes($systemId: ID!) {

View File

@@ -26,6 +26,10 @@ const systemInfoForTesting: InterchangeSystemBuilderArguments = {
parkingSystemId: ChapmanApiBasedParkingRepositoryLoader.id,
useSelfUpdatingEtas: false,
shuttleStopArrivalDegreeDelta: 0.001,
initialRegion: {
topLeft: { latitude: 33.85733, longitude: -117.89553 },
bottomRight: { latitude: 33.73970, longitude: -117.81878 },
},
};
export function buildSystemForTesting() {