chore: added docker files for bringing the container up
This commit is contained in:
1
packages/hoppscotch-backend/.dockerignore
Normal file
1
packages/hoppscotch-backend/.dockerignore
Normal file
@@ -0,0 +1 @@
|
||||
./node_modules
|
||||
30
packages/hoppscotch-backend/Dockerfile
Normal file
30
packages/hoppscotch-backend/Dockerfile
Normal file
@@ -0,0 +1,30 @@
|
||||
FROM node:lts
|
||||
|
||||
WORKDIR /usr/src/app
|
||||
|
||||
# Install pnpm
|
||||
RUN npm i -g pnpm
|
||||
|
||||
# NPM package install
|
||||
COPY package*.json ./
|
||||
RUN pnpm install
|
||||
|
||||
|
||||
# Prisma bits
|
||||
#COPY prisma ./
|
||||
#RUN pnpx prisma generate
|
||||
|
||||
|
||||
COPY . .
|
||||
|
||||
RUN pnpm run build
|
||||
|
||||
EXPOSE 3170
|
||||
EXPOSE 9229
|
||||
|
||||
ENV APP_PORT=${PORT}
|
||||
ENV DB_URL=${DATABASE_URL}
|
||||
ENV PRODUCTION=true
|
||||
#ENV FB_SERVICE_KEY_PATH="secrets/fb-service-key.json"
|
||||
CMD ["pnpm", "run", "start:dev"]
|
||||
#CMD ["./run.sh", "start:dev"]
|
||||
25
packages/hoppscotch-backend/docker-compose.yml
Normal file
25
packages/hoppscotch-backend/docker-compose.yml
Normal file
@@ -0,0 +1,25 @@
|
||||
version: '3.0'
|
||||
services:
|
||||
local:
|
||||
build: ./Dockerfile
|
||||
environment:
|
||||
- PRODUCTION=false
|
||||
- DATABASE_URL=postgresql://postgres:testpass@dev-db:5432/hoppscotch
|
||||
- PORT=3000
|
||||
# volumes:
|
||||
# - .:/usr/src/app
|
||||
depends_on:
|
||||
- dev-db
|
||||
ports:
|
||||
- "3170:3000"
|
||||
- "9229:9229"
|
||||
|
||||
dev-db:
|
||||
image: postgres
|
||||
ports:
|
||||
- "5432:5432"
|
||||
environment:
|
||||
POSTGRES_PASSWORD: testpass
|
||||
POSTGRES_DB: hoppscotch
|
||||
|
||||
|
||||
@@ -21,9 +21,13 @@
|
||||
"test:e2e": "jest --config ./test/jest-e2e.json"
|
||||
},
|
||||
"dependencies": {
|
||||
"@nestjs/apollo": "^10.1.6",
|
||||
"@nestjs/common": "^9.2.1",
|
||||
"@nestjs/core": "^9.2.1",
|
||||
"@nestjs/graphql": "^10.1.6",
|
||||
"@nestjs/platform-express": "^9.2.1",
|
||||
"apollo-server-express": "^3.11.1",
|
||||
"graphql": "^15.5.0",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rimraf": "^3.0.2",
|
||||
"rxjs": "^7.6.0"
|
||||
|
||||
85
packages/hoppscotch-backend/prisma/schema.prisma
Normal file
85
packages/hoppscotch-backend/prisma/schema.prisma
Normal file
@@ -0,0 +1,85 @@
|
||||
datasource db {
|
||||
provider = "postgresql"
|
||||
url = env("DATABASE_URL")
|
||||
}
|
||||
|
||||
generator client {
|
||||
provider = "prisma-client-js"
|
||||
binaryTargets = ["native", "debian-openssl-1.1.x"]
|
||||
}
|
||||
|
||||
model Team {
|
||||
id String @id @default(cuid())
|
||||
name String
|
||||
members TeamMember[]
|
||||
TeamInvitation TeamInvitation[]
|
||||
TeamCollection TeamCollection[]
|
||||
TeamRequest TeamRequest[]
|
||||
TeamEnvironment TeamEnvironment[]
|
||||
}
|
||||
|
||||
model TeamMember {
|
||||
id String @id @default(uuid()) // Membership ID
|
||||
role TeamMemberRole
|
||||
userUid String
|
||||
teamID String
|
||||
team Team @relation(fields: [teamID], references: [id], onDelete: Cascade)
|
||||
|
||||
@@unique([teamID, userUid])
|
||||
}
|
||||
|
||||
model TeamInvitation {
|
||||
id String @id @default(cuid())
|
||||
teamID String
|
||||
team Team @relation(fields: [teamID], references: [id], onDelete: Cascade)
|
||||
creatorUid String
|
||||
inviteeEmail String
|
||||
inviteeRole TeamMemberRole
|
||||
|
||||
@@unique([teamID, inviteeEmail])
|
||||
@@index([teamID])
|
||||
}
|
||||
|
||||
model TeamCollection {
|
||||
id String @id @default(cuid())
|
||||
parentID String?
|
||||
parent TeamCollection? @relation("TeamCollectionChildParent", fields: [parentID], references: [id])
|
||||
children TeamCollection[] @relation("TeamCollectionChildParent")
|
||||
requests TeamRequest[]
|
||||
teamID String
|
||||
team Team @relation(fields: [teamID], references: [id], onDelete: Cascade)
|
||||
title String
|
||||
}
|
||||
|
||||
model TeamRequest {
|
||||
id String @id @default(cuid())
|
||||
collectionID String
|
||||
collection TeamCollection @relation(fields: [collectionID], references: [id], onDelete: Cascade)
|
||||
teamID String
|
||||
team Team @relation(fields: [teamID], references: [id], onDelete: Cascade)
|
||||
title String
|
||||
request Json
|
||||
}
|
||||
|
||||
model Shortcode {
|
||||
id String @id
|
||||
request Json
|
||||
creatorUid String?
|
||||
createdOn DateTime @default(now())
|
||||
|
||||
@@unique(fields: [id, creatorUid], name: "creator_uid_shortcode_unique")
|
||||
}
|
||||
|
||||
model TeamEnvironment {
|
||||
id String @id @default(cuid())
|
||||
teamID String
|
||||
team Team @relation(fields: [teamID], references: [id], onDelete: Cascade)
|
||||
name String
|
||||
variables Json
|
||||
}
|
||||
|
||||
enum TeamMemberRole {
|
||||
OWNER
|
||||
VIEWER
|
||||
EDITOR
|
||||
}
|
||||
@@ -1,10 +1,51 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { AppController } from './app.controller';
|
||||
import { AppService } from './app.service';
|
||||
import { GraphQLModule } from '@nestjs/graphql';
|
||||
import { ApolloDriver, ApolloDriverConfig } from '@nestjs/apollo';
|
||||
import { AppResolver } from './app.resolver';
|
||||
|
||||
@Module({
|
||||
imports: [],
|
||||
imports: [
|
||||
GraphQLModule.forRoot<ApolloDriverConfig>({
|
||||
playground: process.env.PRODUCTION !== 'true',
|
||||
debug: process.env.PRODUCTION !== 'true',
|
||||
autoSchemaFile: true,
|
||||
installSubscriptionHandlers: true,
|
||||
subscriptions: {
|
||||
'subscriptions-transport-ws': {
|
||||
path: '/graphql',
|
||||
onConnect: (connectionParams: any) => {
|
||||
return {
|
||||
reqHeaders: Object.fromEntries(
|
||||
Object.entries(connectionParams).map(([k, v]) => [
|
||||
k.toLowerCase(),
|
||||
v,
|
||||
]),
|
||||
),
|
||||
};
|
||||
},
|
||||
},
|
||||
},
|
||||
context: async ({ req, connection }) => {
|
||||
if (req) {
|
||||
return { reqHeaders: req.headers };
|
||||
} else {
|
||||
return {
|
||||
// Lowercase the keys
|
||||
reqHeaders: Object.fromEntries(
|
||||
Object.entries(connection.context).map(([k, v]) => [
|
||||
k.toLowerCase(),
|
||||
v,
|
||||
]),
|
||||
),
|
||||
};
|
||||
}
|
||||
},
|
||||
driver: ApolloDriver,
|
||||
}),
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [AppService],
|
||||
providers: [AppService, AppResolver],
|
||||
})
|
||||
export class AppModule {}
|
||||
|
||||
12
packages/hoppscotch-backend/src/app.resolver.ts
Normal file
12
packages/hoppscotch-backend/src/app.resolver.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { Query, Resolver } from '@nestjs/graphql';
|
||||
import { AppService } from './app.service';
|
||||
|
||||
@Resolver((of) => String)
|
||||
export class AppResolver {
|
||||
constructor(private appService: AppService) {}
|
||||
|
||||
@Query((returns) => String)
|
||||
async author() {
|
||||
return this.appService.getHello();
|
||||
}
|
||||
}
|
||||
211
packages/hoppscotch-backend/src/errors.ts
Normal file
211
packages/hoppscotch-backend/src/errors.ts
Normal file
@@ -0,0 +1,211 @@
|
||||
export const INVALID_EMAIL = 'invalid/email' as const;
|
||||
|
||||
export const EMAIL_FAILED = 'email/failed' as const;
|
||||
|
||||
/**
|
||||
* Token Authorization failed (Check 'Authorization' Header)
|
||||
* (GqlAuthGuard)
|
||||
*/
|
||||
export const AUTH_FAIL = 'auth/fail';
|
||||
|
||||
/**
|
||||
* Tried to delete an user data document from fb firestore but failed.
|
||||
* (FirebaseService)
|
||||
*/
|
||||
export const USER_FB_DOCUMENT_DELETION_FAILED =
|
||||
'fb/firebase_document_deletion_failed' as const;
|
||||
|
||||
/**
|
||||
* Tried to do an action on a user where user is not found
|
||||
*/
|
||||
export const USER_NOT_FOUND = 'user/not_found' as const;
|
||||
|
||||
/**
|
||||
* User deletion failure
|
||||
* (UserService)
|
||||
*/
|
||||
export const USER_DELETION_FAILED = 'user/deletion_failed' as const;
|
||||
|
||||
/**
|
||||
* User deletion failure error due to user being a team owner
|
||||
* (UserService)
|
||||
*/
|
||||
export const USER_IS_OWNER = 'user/is_owner' as const;
|
||||
|
||||
/**
|
||||
* Tried to perform action on a team which they are not a member of
|
||||
* (GqlTeamMemberGuard)
|
||||
*/
|
||||
export const TEAM_MEMBER_NOT_FOUND = 'team/member_not_found' as const;
|
||||
|
||||
/**
|
||||
* Tried to perform action on a team that doesn't accept their member role level
|
||||
* (GqlTeamMemberGuard)
|
||||
*/
|
||||
export const TEAM_NOT_REQUIRED_ROLE = 'team/not_required_role' as const;
|
||||
|
||||
/**
|
||||
* Team name validation failure
|
||||
* (TeamService)
|
||||
*/
|
||||
export const TEAM_NAME_INVALID = 'team/name_invalid';
|
||||
|
||||
/**
|
||||
* Couldn't find the sync data from the user
|
||||
* (TeamCollectionService)
|
||||
*/
|
||||
export const TEAM_USER_NO_FB_SYNCDATA = 'team/user_no_fb_syncdata';
|
||||
|
||||
/**
|
||||
* There was a problem resolving the firebase collection path
|
||||
* (TeamCollectionService)
|
||||
*/
|
||||
export const TEAM_FB_COLL_PATH_RESOLVE_FAIL = 'team/fb_coll_path_resolve_fail';
|
||||
|
||||
/**
|
||||
* Tried to update the team to a state it doesn't have any owners
|
||||
* (TeamService)
|
||||
*/
|
||||
export const TEAM_ONLY_ONE_OWNER = 'team/only_one_owner';
|
||||
|
||||
/**
|
||||
* Invalid or non-existent Team ID
|
||||
* (TeamService)
|
||||
*/
|
||||
export const TEAM_INVALID_ID = 'team/invalid_id' as const;
|
||||
|
||||
/**
|
||||
* Invalid or non-existent collection id
|
||||
* (GqlCollectionTeamMemberGuard)
|
||||
*/
|
||||
export const TEAM_INVALID_COLL_ID = 'team/invalid_coll_id' as const;
|
||||
|
||||
/**
|
||||
* Invalid team id or user id
|
||||
* (TeamService)
|
||||
*/
|
||||
export const TEAM_INVALID_ID_OR_USER = 'team/invalid_id_or_user';
|
||||
|
||||
/**
|
||||
* The provided title for the team collection is short (less than 3 characters)
|
||||
* (TeamCollectionService)
|
||||
*/
|
||||
export const TEAM_COLL_SHORT_TITLE = 'team_coll/short_title';
|
||||
|
||||
/**
|
||||
* The JSON used is not valid
|
||||
* (TeamCollectionService)
|
||||
*/
|
||||
export const TEAM_COLL_INVALID_JSON = 'team_coll/invalid_json';
|
||||
|
||||
/**
|
||||
* Tried to perform action on a request that doesn't accept their member role level
|
||||
* (GqlRequestTeamMemberGuard)
|
||||
*/
|
||||
export const TEAM_REQ_NOT_REQUIRED_ROLE = 'team_req/not_required_role';
|
||||
|
||||
/**
|
||||
* Tried to operate on a request which does not exist
|
||||
* (TeamRequestService)
|
||||
*/
|
||||
export const TEAM_REQ_NOT_FOUND = 'team_req/not_found' as const;
|
||||
|
||||
/**
|
||||
* Invalid or non-existent collection id
|
||||
* (TeamRequestService)
|
||||
*/
|
||||
export const TEAM_REQ_INVALID_TARGET_COLL_ID =
|
||||
'team_req/invalid_target_id' as const;
|
||||
|
||||
/**
|
||||
* Tried to perform action on a request when the user is not even member of the team
|
||||
* (GqlRequestTeamMemberGuard, GqlCollectionTeamMemberGuard)
|
||||
*/
|
||||
export const TEAM_REQ_NOT_MEMBER = 'team_req/not_member';
|
||||
|
||||
export const TEAM_INVITE_MEMBER_HAS_INVITE =
|
||||
'team_invite/member_has_invite' as const;
|
||||
|
||||
export const TEAM_INVITE_NO_INVITE_FOUND =
|
||||
'team_invite/no_invite_found' as const;
|
||||
|
||||
export const TEAM_INVITE_ALREADY_MEMBER = 'team_invite/already_member' as const;
|
||||
|
||||
export const TEAM_INVITE_EMAIL_DO_NOT_MATCH =
|
||||
'team_invite/email_do_not_match' as const;
|
||||
|
||||
export const TEAM_INVITE_NOT_VALID_VIEWER =
|
||||
'team_invite/not_valid_viewer' as const;
|
||||
|
||||
export const SHORTCODE_NOT_FOUND = 'shortcode/not_found' as const;
|
||||
|
||||
export const SHORTCODE_INVALID_JSON = 'shortcode/invalid_json' as const;
|
||||
|
||||
/**
|
||||
* Invalid or non-existent TEAM ENVIRONMMENT ID
|
||||
* (TeamEnvironmentsService)
|
||||
*/
|
||||
export const TEAM_ENVIRONMMENT_NOT_FOUND =
|
||||
'team_environment/not_found' as const;
|
||||
|
||||
/**
|
||||
* The user is not a member of the team of the given environment
|
||||
* (GqlTeamEnvTeamGuard)
|
||||
*/
|
||||
export const TEAM_ENVIRONMENT_NOT_TEAM_MEMBER =
|
||||
'team_environment/not_team_member' as const;
|
||||
|
||||
/*
|
||||
|
||||
|------------------------------------|
|
||||
|Server errors that are actually bugs|
|
||||
|------------------------------------|
|
||||
|
||||
*/
|
||||
|
||||
/**
|
||||
* Couldn't find user data from the GraphQL context (Check if GqlAuthGuard is applied)
|
||||
* (GqlTeamMemberGuard, GqlCollectionTeamMemberGuard)
|
||||
*/
|
||||
export const BUG_AUTH_NO_USER_CTX = 'bug/auth/auth_no_user_ctx' as const;
|
||||
|
||||
/**
|
||||
* Couldn't find teamID parameter in the attached GraphQL operation. (Check if teamID is present)
|
||||
* (GqlTeamMemberGuard, GQLEAAdminGuard, GqlCollectionTeamMemberGuard)
|
||||
*/
|
||||
export const BUG_TEAM_NO_TEAM_ID = 'bug/team/no_team_id';
|
||||
|
||||
/**
|
||||
* Couldn't find RequireTeamRole decorator. (Check if it is applied)
|
||||
* (GqlTeamMemberGuard)
|
||||
*/
|
||||
export const BUG_TEAM_NO_REQUIRE_TEAM_ROLE = 'bug/team/no_require_team_role';
|
||||
|
||||
/**
|
||||
* Couldn't find 'collectionID' param to the attached GQL operation. (Check if exists)
|
||||
* (GqlCollectionTeamMemberGuard)
|
||||
*/
|
||||
export const BUG_TEAM_COLL_NO_COLL_ID = 'bug/team_coll/no_coll_id';
|
||||
|
||||
/**
|
||||
* Couldn't find 'requestID' param to the attached GQL operation. (Check if exists)
|
||||
* (GqlRequestTeamMemberGuard)
|
||||
*/
|
||||
export const BUG_TEAM_REQ_NO_REQ_ID = 'bug/team_req/no_req_id';
|
||||
|
||||
export const BUG_TEAM_INVITE_NO_INVITE_ID =
|
||||
'bug/team_invite/no_invite_id' as const;
|
||||
|
||||
/**
|
||||
* Couldn't find RequireTeamRole decorator. (Check if it is applied)
|
||||
* (GqlTeamEnvTeamGuard)
|
||||
*/
|
||||
export const BUG_TEAM_ENV_GUARD_NO_REQUIRE_ROLES =
|
||||
'bug/team_env/guard_no_require_roles' as const;
|
||||
|
||||
/**
|
||||
* Couldn't find 'id' param to the operation. (Check if it is applied)
|
||||
* (GqlTeamEnvTeamGuard)
|
||||
*/
|
||||
export const BUG_TEAM_ENV_GUARD_NO_ENV_ID =
|
||||
'bug/team_env/guard_no_env_id' as const;
|
||||
@@ -1,8 +1,40 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { json } from 'express';
|
||||
import { AppModule } from './app.module';
|
||||
|
||||
async function bootstrap() {
|
||||
console.log(`Running in production: ${process.env.PRODUCTION}`);
|
||||
console.log(`Port: ${process.env.PORT}`);
|
||||
console.log(`Database: ${process.env.DATABASE_URL}`);
|
||||
|
||||
const app = await NestFactory.create(AppModule);
|
||||
await app.listen(3000);
|
||||
|
||||
// Increase fil upload limit to 50MB
|
||||
app.use(
|
||||
json({
|
||||
limit: '100mb',
|
||||
}),
|
||||
);
|
||||
|
||||
if (process.env.PRODUCTION === 'false') {
|
||||
console.log('Enabling CORS with development settings');
|
||||
app.enableCors({
|
||||
origin: true,
|
||||
});
|
||||
} else {
|
||||
console.log('Enabling CORS with production settings');
|
||||
|
||||
// HACK: Temporary fix for Liyas to work on production directly :P
|
||||
/*
|
||||
app.enableCors({
|
||||
origin: /hoppscotch\.io$/
|
||||
});
|
||||
*/
|
||||
|
||||
app.enableCors({
|
||||
origin: true,
|
||||
});
|
||||
}
|
||||
await app.listen(process.env.PORT || 3170);
|
||||
}
|
||||
bootstrap();
|
||||
|
||||
Reference in New Issue
Block a user