Compare commits
15 Commits
feat/migra
...
feat/subpa
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a134e20cdf | ||
|
|
a501282f2b | ||
|
|
eb248fa0df | ||
|
|
f1e4ac7fc4 | ||
|
|
28059ddc60 | ||
|
|
64517a53af | ||
|
|
79a9285f93 | ||
|
|
aebcbac979 | ||
|
|
d19d96ba9c | ||
|
|
312940009e | ||
|
|
8703a0dcfd | ||
|
|
03e21e0b0c | ||
|
|
d6e4b6497f | ||
|
|
2f1fca2917 | ||
|
|
04092d8597 |
@@ -59,3 +59,6 @@ VITE_BACKEND_API_URL=http://localhost:3170/v1
|
||||
# Terms Of Service And Privacy Policy Links (Optional)
|
||||
VITE_APP_TOS_LINK=https://docs.hoppscotch.io/support/terms
|
||||
VITE_APP_PRIVACY_POLICY_LINK=https://docs.hoppscotch.io/support/privacy
|
||||
|
||||
# Set to `true` for subpath based access
|
||||
ENABLE_SUBPATH_BASED_ACCESS=false
|
||||
|
||||
14
.vscode/extensions.json
vendored
Normal file
14
.vscode/extensions.json
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"antfu.iconify",
|
||||
"vue.volar",
|
||||
"esbenp.prettier-vscode",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"editorconfig.editorconfig",
|
||||
"csstools.postcss",
|
||||
"folke.vscode-monorepo-workspace"
|
||||
],
|
||||
"unwantedRecommendations": [
|
||||
"octref.vetur"
|
||||
]
|
||||
}
|
||||
11
Caddyfile
Normal file
11
Caddyfile
Normal file
@@ -0,0 +1,11 @@
|
||||
:3500 {
|
||||
handle_path /admin* {
|
||||
reverse_proxy localhost:3100
|
||||
}
|
||||
|
||||
handle_path /backend* {
|
||||
reverse_proxy localhost:3170
|
||||
}
|
||||
|
||||
reverse_proxy localhost:3000 # Proxy other requests to your server running on port 3001
|
||||
}
|
||||
15
aio-multiport-setup.Caddyfile
Normal file
15
aio-multiport-setup.Caddyfile
Normal file
@@ -0,0 +1,15 @@
|
||||
:3000 {
|
||||
try_files {path} /
|
||||
root * /site/selfhost-web
|
||||
file_server
|
||||
}
|
||||
|
||||
:3100 {
|
||||
try_files {path} /
|
||||
root * /site/sh-admin
|
||||
file_server
|
||||
}
|
||||
|
||||
:80 {
|
||||
respond 404
|
||||
}
|
||||
33
aio-subpath-access.Caddyfile
Normal file
33
aio-subpath-access.Caddyfile
Normal file
@@ -0,0 +1,33 @@
|
||||
:3000 {
|
||||
respond 404
|
||||
}
|
||||
|
||||
:3100 {
|
||||
respond 404
|
||||
}
|
||||
|
||||
:80 {
|
||||
# Serve the `selfhost-web` SPA by default
|
||||
root * /site/selfhost-web
|
||||
file_server
|
||||
|
||||
handle_path /admin* {
|
||||
root * /site/sh-admin-subpath-access
|
||||
file_server
|
||||
|
||||
# Ensures any non-existent file in the server is routed to the SPA
|
||||
try_files {path} /
|
||||
}
|
||||
|
||||
# Handle requests under `/backend*` path
|
||||
handle_path /backend* {
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
|
||||
# Catch-all route for unknown paths, serves `selfhost-web` SPA
|
||||
handle {
|
||||
root * /site/selfhost-web
|
||||
file_server
|
||||
try_files {path} /
|
||||
}
|
||||
}
|
||||
@@ -1,11 +0,0 @@
|
||||
:3000 {
|
||||
try_files {path} /
|
||||
root * /site/selfhost-web
|
||||
file_server
|
||||
}
|
||||
|
||||
:3100 {
|
||||
try_files {path} /
|
||||
root * /site/sh-admin
|
||||
file_server
|
||||
}
|
||||
@@ -49,8 +49,9 @@ execSync(`npx import-meta-env -x build.env -e build.env -p "/site/**/*"`)
|
||||
|
||||
fs.rmSync("build.env")
|
||||
|
||||
const caddyProcess = runChildProcessWithPrefix("caddy", ["run", "--config", "/etc/caddy/Caddyfile", "--adapter", "caddyfile"], "App/Admin Dashboard Caddy")
|
||||
const backendProcess = runChildProcessWithPrefix("pnpm", ["run", "start:migrate:prod"], "Backend Server")
|
||||
const caddyFileName = process.env.ENABLE_SUBPATH_BASED_ACCESS === 'true' ? 'aio-subpath-access.Caddyfile' : 'aio-multiport-setup.Caddyfile'
|
||||
const caddyProcess = runChildProcessWithPrefix("caddy", ["run", "--config", `/etc/caddy/${caddyFileName}`, "--adapter", "caddyfile"], "App/Admin Dashboard Caddy")
|
||||
const backendProcess = runChildProcessWithPrefix("pnpm", ["run", "start:prod"], "Backend Server")
|
||||
|
||||
caddyProcess.on("exit", (code) => {
|
||||
console.log(`Exiting process because Caddy Server exited with code ${code}`)
|
||||
|
||||
@@ -17,7 +17,7 @@ services:
|
||||
environment:
|
||||
# Edit the below line to match your PostgresDB URL if you have an outside DB (make sure to update the .env file as well)
|
||||
- DATABASE_URL=postgresql://postgres:testpass@hoppscotch-db:5432/hoppscotch?connect_timeout=300
|
||||
- PORT=3170
|
||||
- PORT=8080
|
||||
volumes:
|
||||
# Uncomment the line below when modifying code. Only applicable when using the "dev" target.
|
||||
# - ./packages/hoppscotch-backend/:/usr/src/app
|
||||
@@ -26,6 +26,7 @@ services:
|
||||
hoppscotch-db:
|
||||
condition: service_healthy
|
||||
ports:
|
||||
- "3180:80"
|
||||
- "3170:3170"
|
||||
|
||||
# The main hoppscotch app. This will be hosted at port 3000
|
||||
@@ -42,7 +43,8 @@ services:
|
||||
depends_on:
|
||||
- hoppscotch-backend
|
||||
ports:
|
||||
- "3000:8080"
|
||||
- "3080:80"
|
||||
- "3000:3000"
|
||||
|
||||
# The Self Host dashboard for managing the app. This will be hosted at port 3100
|
||||
# NOTE: To do TLS or play around with how the app is hosted, you can look into the Caddyfile for
|
||||
@@ -58,7 +60,8 @@ services:
|
||||
depends_on:
|
||||
- hoppscotch-backend
|
||||
ports:
|
||||
- "3100:8080"
|
||||
- "3280:80"
|
||||
- "3100:3100"
|
||||
|
||||
# The service that spins up all 3 services at once in one container
|
||||
hoppscotch-aio:
|
||||
@@ -75,7 +78,8 @@ services:
|
||||
ports:
|
||||
- "3000:3000"
|
||||
- "3100:3100"
|
||||
- "3170:3170"
|
||||
- "3170:8080"
|
||||
- "3080:80"
|
||||
|
||||
# The preset DB service, you can delete/comment the below lines if
|
||||
# you are using an external postgres instance
|
||||
|
||||
0
individual-containers-subpath-access.Caddyfile
Normal file
0
individual-containers-subpath-access.Caddyfile
Normal file
3
packages/hoppscotch-backend/Caddyfile
Normal file
3
packages/hoppscotch-backend/Caddyfile
Normal file
@@ -0,0 +1,3 @@
|
||||
:3170 {
|
||||
reverse_proxy localhost:80
|
||||
}
|
||||
@@ -23,7 +23,7 @@ FROM builder AS dev
|
||||
|
||||
ENV PRODUCTION="false"
|
||||
|
||||
CMD ["pnpm", "run", "start:migrate:dev"]
|
||||
CMD ["pnpm", "run", "start:dev"]
|
||||
|
||||
EXPOSE 3170
|
||||
|
||||
@@ -32,7 +32,7 @@ FROM builder AS prod
|
||||
|
||||
ENV PRODUCTION="true"
|
||||
|
||||
CMD ["pnpm", "run", "start:migrate:prod"]
|
||||
CMD ["pnpm", "run", "start:prod"]
|
||||
|
||||
EXPOSE 3170
|
||||
|
||||
|
||||
3
packages/hoppscotch-backend/backend-multiport.Caddyfile
Normal file
3
packages/hoppscotch-backend/backend-multiport.Caddyfile
Normal file
@@ -0,0 +1,3 @@
|
||||
:80 :3170 {
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
7
packages/hoppscotch-backend/backend-subpath.Caddyfile
Normal file
7
packages/hoppscotch-backend/backend-subpath.Caddyfile
Normal file
@@ -0,0 +1,7 @@
|
||||
:80 :3170 {
|
||||
handle_path /backend* {
|
||||
reverse_proxy localhost:8080
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -14,9 +14,6 @@
|
||||
"start:dev": "nest start --watch",
|
||||
"start:debug": "nest start --debug --watch",
|
||||
"start:prod": "node dist/main",
|
||||
"start:migrate:dev": "prisma migrate deploy && npm run start:dev",
|
||||
"start:migrate:debug": "prisma migrate deploy && npm run start:debug",
|
||||
"start:migrate:prod": "prisma migrate deploy && npm run start:prod",
|
||||
"lint": "eslint \"{src,apps,libs,test}/**/*.ts\" --fix",
|
||||
"test": "jest",
|
||||
"postinstall": "prisma generate && pnpm run generate-gql-sdl",
|
||||
|
||||
71
packages/hoppscotch-backend/prod_run.mjs
Normal file
71
packages/hoppscotch-backend/prod_run.mjs
Normal file
@@ -0,0 +1,71 @@
|
||||
#!/usr/local/bin/node
|
||||
// @ts-check
|
||||
|
||||
import { execSync, spawn } from 'child_process';
|
||||
import fs from 'fs';
|
||||
import process from 'process';
|
||||
|
||||
function runChildProcessWithPrefix(command, args, prefix) {
|
||||
const childProcess = spawn(command, args);
|
||||
|
||||
childProcess.stdout.on('data', (data) => {
|
||||
const output = data.toString().trim().split('\n');
|
||||
output.forEach((line) => {
|
||||
console.log(`${prefix} | ${line}`);
|
||||
});
|
||||
});
|
||||
|
||||
childProcess.stderr.on('data', (data) => {
|
||||
const error = data.toString().trim().split('\n');
|
||||
error.forEach((line) => {
|
||||
console.error(`${prefix} | ${line}`);
|
||||
});
|
||||
});
|
||||
|
||||
childProcess.on('close', (code) => {
|
||||
console.log(`${prefix} Child process exited with code ${code}`);
|
||||
});
|
||||
|
||||
childProcess.on('error', (stuff) => {
|
||||
console.log('error');
|
||||
console.log(stuff);
|
||||
});
|
||||
|
||||
return childProcess;
|
||||
}
|
||||
|
||||
const caddyFileName =
|
||||
process.env.ENABLE_SUBPATH_BASED_ACCESS === 'true'
|
||||
? 'backend-subpath.Caddyfile'
|
||||
: 'backend-multiport.Caddyfile';
|
||||
const caddyProcess = runChildProcessWithPrefix(
|
||||
'caddy',
|
||||
['run', '--config', `/etc/caddy/${caddyFileName}`, '--adapter', 'caddyfile'],
|
||||
'App/Admin Dashboard Caddy',
|
||||
);
|
||||
const backendProcess = runChildProcessWithPrefix(
|
||||
'pnpm',
|
||||
['run', 'start:prod'],
|
||||
'Backend Server',
|
||||
);
|
||||
|
||||
caddyProcess.on('exit', (code) => {
|
||||
console.log(`Exiting process because Caddy Server exited with code ${code}`);
|
||||
process.exit(code);
|
||||
});
|
||||
|
||||
backendProcess.on('exit', (code) => {
|
||||
console.log(
|
||||
`Exiting process because Backend Server exited with code ${code}`,
|
||||
);
|
||||
process.exit(code);
|
||||
});
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('SIGINT received, exiting...');
|
||||
|
||||
caddyProcess.kill('SIGINT');
|
||||
backendProcess.kill('SIGINT');
|
||||
|
||||
process.exit(0);
|
||||
});
|
||||
@@ -11,7 +11,6 @@ import { TeamEnvironmentsModule } from '../team-environments/team-environments.m
|
||||
import { TeamCollectionModule } from '../team-collection/team-collection.module';
|
||||
import { TeamRequestModule } from '../team-request/team-request.module';
|
||||
import { InfraResolver } from './infra.resolver';
|
||||
import { ShortcodeModule } from 'src/shortcode/shortcode.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@@ -24,7 +23,6 @@ import { ShortcodeModule } from 'src/shortcode/shortcode.module';
|
||||
TeamEnvironmentsModule,
|
||||
TeamCollectionModule,
|
||||
TeamRequestModule,
|
||||
ShortcodeModule,
|
||||
],
|
||||
providers: [InfraResolver, AdminResolver, AdminService],
|
||||
exports: [AdminService],
|
||||
|
||||
@@ -443,23 +443,6 @@ export class AdminResolver {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Mutation(() => Boolean, {
|
||||
description: 'Revoke Shortcode by ID',
|
||||
})
|
||||
@UseGuards(GqlAuthGuard, GqlAdminGuard)
|
||||
async revokeShortcodeByAdmin(
|
||||
@Args({
|
||||
name: 'code',
|
||||
description: 'The shortcode to delete',
|
||||
type: () => ID,
|
||||
})
|
||||
code: string,
|
||||
): Promise<boolean> {
|
||||
const res = await this.adminService.deleteShortcode(code);
|
||||
if (E.isLeft(res)) throwErr(res.left);
|
||||
return true;
|
||||
}
|
||||
|
||||
/* Subscriptions */
|
||||
|
||||
@Subscription(() => InvitedUser, {
|
||||
|
||||
@@ -15,7 +15,6 @@ import {
|
||||
INVALID_EMAIL,
|
||||
USER_ALREADY_INVITED,
|
||||
} from '../errors';
|
||||
import { ShortcodeService } from 'src/shortcode/shortcode.service';
|
||||
|
||||
const mockPrisma = mockDeep<PrismaService>();
|
||||
const mockPubSub = mockDeep<PubSubService>();
|
||||
@@ -26,7 +25,6 @@ const mockTeamRequestService = mockDeep<TeamRequestService>();
|
||||
const mockTeamInvitationService = mockDeep<TeamInvitationService>();
|
||||
const mockTeamCollectionService = mockDeep<TeamCollectionService>();
|
||||
const mockMailerService = mockDeep<MailerService>();
|
||||
const mockShortcodeService = mockDeep<ShortcodeService>();
|
||||
|
||||
const adminService = new AdminService(
|
||||
mockUserService,
|
||||
@@ -38,7 +36,6 @@ const adminService = new AdminService(
|
||||
mockPubSub as any,
|
||||
mockPrisma as any,
|
||||
mockMailerService,
|
||||
mockShortcodeService,
|
||||
);
|
||||
|
||||
const invitedUsers: InvitedUsers[] = [
|
||||
|
||||
@@ -24,7 +24,6 @@ import { TeamRequestService } from '../team-request/team-request.service';
|
||||
import { TeamEnvironmentsService } from '../team-environments/team-environments.service';
|
||||
import { TeamInvitationService } from '../team-invitation/team-invitation.service';
|
||||
import { TeamMemberRole } from '../team/team.model';
|
||||
import { ShortcodeService } from 'src/shortcode/shortcode.service';
|
||||
|
||||
@Injectable()
|
||||
export class AdminService {
|
||||
@@ -38,7 +37,6 @@ export class AdminService {
|
||||
private readonly pubsub: PubSubService,
|
||||
private readonly prisma: PrismaService,
|
||||
private readonly mailerService: MailerService,
|
||||
private readonly shortcodeService: ShortcodeService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -434,35 +432,4 @@ export class AdminService {
|
||||
|
||||
return E.right(teamInvite.right);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all created ShortCodes
|
||||
*
|
||||
* @param args Pagination arguments
|
||||
* @param userEmail User email
|
||||
* @returns ShortcodeWithUserEmail
|
||||
*/
|
||||
async fetchAllShortcodes(
|
||||
cursorID: string,
|
||||
take: number,
|
||||
userEmail: string = null,
|
||||
) {
|
||||
return this.shortcodeService.fetchAllShortcodes(
|
||||
{ cursor: cursorID, take },
|
||||
userEmail,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a Shortcode
|
||||
*
|
||||
* @param shortcodeID ID of Shortcode being deleted
|
||||
* @returns Boolean on successful deletion
|
||||
*/
|
||||
async deleteShortcode(shortcodeID: string) {
|
||||
const result = await this.shortcodeService.deleteShortcode(shortcodeID);
|
||||
|
||||
if (E.isLeft(result)) return E.left(result.left);
|
||||
return E.right(result.right);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,6 @@ import { InvitedUser } from './invited-user.model';
|
||||
import { Team } from 'src/team/team.model';
|
||||
import { TeamInvitation } from 'src/team-invitation/team-invitation.model';
|
||||
import { GqlAdmin } from './decorators/gql-admin.decorator';
|
||||
import { ShortcodeWithUserEmail } from 'src/shortcode/shortcode.model';
|
||||
|
||||
@UseGuards(GqlThrottlerGuard)
|
||||
@Resolver(() => Infra)
|
||||
@@ -203,23 +202,4 @@ export class InfraResolver {
|
||||
async teamRequestsCount() {
|
||||
return this.adminService.getTeamRequestsCount();
|
||||
}
|
||||
|
||||
@ResolveField(() => [ShortcodeWithUserEmail], {
|
||||
description: 'Returns a list of all the shortcodes in the infra',
|
||||
})
|
||||
async allShortcodes(
|
||||
@Args() args: PaginationArgs,
|
||||
@Args({
|
||||
name: 'userEmail',
|
||||
nullable: true,
|
||||
description: 'Users email to filter shortcodes by',
|
||||
})
|
||||
userEmail: string,
|
||||
) {
|
||||
return await this.adminService.fetchAllShortcodes(
|
||||
args.cursor,
|
||||
args.take,
|
||||
userEmail,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { Field, ID, ObjectType } from '@nestjs/graphql';
|
||||
import { User } from 'src/user/user.model';
|
||||
|
||||
@ObjectType()
|
||||
export class Shortcode {
|
||||
@@ -24,46 +23,3 @@ export class Shortcode {
|
||||
})
|
||||
createdOn: Date;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class ShortcodeCreator {
|
||||
@Field({
|
||||
description: 'Uid of user who created the shortcode',
|
||||
})
|
||||
uid: string;
|
||||
|
||||
@Field({
|
||||
description: 'Email of user who created the shortcode',
|
||||
})
|
||||
email: string;
|
||||
}
|
||||
|
||||
@ObjectType()
|
||||
export class ShortcodeWithUserEmail {
|
||||
@Field(() => ID, {
|
||||
description: 'The 12 digit alphanumeric code',
|
||||
})
|
||||
id: string;
|
||||
|
||||
@Field({
|
||||
description: 'JSON string representing the request data',
|
||||
})
|
||||
request: string;
|
||||
|
||||
@Field({
|
||||
description: 'JSON string representing the properties for an embed',
|
||||
nullable: true,
|
||||
})
|
||||
properties: string;
|
||||
|
||||
@Field({
|
||||
description: 'Timestamp of when the Shortcode was created',
|
||||
})
|
||||
createdOn: Date;
|
||||
|
||||
@Field({
|
||||
description: 'Details of user who created the shortcode',
|
||||
nullable: true,
|
||||
})
|
||||
creator: ShortcodeCreator;
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import {
|
||||
} from '@nestjs/graphql';
|
||||
import * as E from 'fp-ts/Either';
|
||||
import { UseGuards } from '@nestjs/common';
|
||||
import { Shortcode, ShortcodeWithUserEmail } from './shortcode.model';
|
||||
import { Shortcode } from './shortcode.model';
|
||||
import { ShortcodeService } from './shortcode.service';
|
||||
import { throwErr } from 'src/utils';
|
||||
import { GqlUser } from 'src/decorators/gql-user.decorator';
|
||||
@@ -19,7 +19,6 @@ import { AuthUser } from '../types/AuthUser';
|
||||
import { PaginationArgs } from 'src/types/input-types.args';
|
||||
import { GqlThrottlerGuard } from 'src/guards/gql-throttler.guard';
|
||||
import { SkipThrottle } from '@nestjs/throttler';
|
||||
import { GqlAdminGuard } from 'src/admin/guards/gql-admin.guard';
|
||||
|
||||
@UseGuards(GqlThrottlerGuard)
|
||||
@Resolver(() => Shortcode)
|
||||
@@ -122,7 +121,7 @@ export class ShortcodeResolver {
|
||||
@Args({
|
||||
name: 'code',
|
||||
type: () => ID,
|
||||
description: 'The shortcode to remove',
|
||||
description: 'The shortcode to resolve',
|
||||
})
|
||||
code: string,
|
||||
) {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
import { mockDeep, mockReset } from 'jest-mock-extended';
|
||||
import { PrismaService } from '../prisma/prisma.service';
|
||||
import {
|
||||
INVALID_EMAIL,
|
||||
SHORTCODE_INVALID_PROPERTIES_JSON,
|
||||
SHORTCODE_INVALID_REQUEST_JSON,
|
||||
SHORTCODE_NOT_FOUND,
|
||||
SHORTCODE_PROPERTIES_NOT_FOUND,
|
||||
} from 'src/errors';
|
||||
import { Shortcode, ShortcodeWithUserEmail } from './shortcode.model';
|
||||
import { Shortcode } from './shortcode.model';
|
||||
import { ShortcodeService } from './shortcode.service';
|
||||
import { UserService } from 'src/user/user.service';
|
||||
import { AuthUser } from 'src/types/AuthUser';
|
||||
@@ -98,35 +97,6 @@ const shortcodes = [
|
||||
},
|
||||
];
|
||||
|
||||
const shortcodesWithUserEmail = [
|
||||
{
|
||||
id: 'blablabla',
|
||||
request: {
|
||||
hello: 'there',
|
||||
},
|
||||
embedProperties: {
|
||||
foo: 'bar',
|
||||
},
|
||||
creatorUid: user.uid,
|
||||
createdOn: new Date(),
|
||||
updatedOn: createdOn,
|
||||
User: user,
|
||||
},
|
||||
{
|
||||
id: 'blablabla1',
|
||||
request: {
|
||||
hello: 'there',
|
||||
},
|
||||
embedProperties: {
|
||||
foo: 'bar',
|
||||
},
|
||||
creatorUid: user.uid,
|
||||
createdOn: new Date(),
|
||||
updatedOn: createdOn,
|
||||
User: user,
|
||||
},
|
||||
];
|
||||
|
||||
describe('ShortcodeService', () => {
|
||||
describe('getShortCode', () => {
|
||||
test('should return a valid Shortcode with valid Shortcode ID', async () => {
|
||||
@@ -471,99 +441,4 @@ describe('ShortcodeService', () => {
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('deleteShortcode', () => {
|
||||
test('should return true on successful deletion of Shortcode with valid inputs', async () => {
|
||||
mockPrisma.shortcode.delete.mockResolvedValueOnce(mockEmbed);
|
||||
|
||||
const result = await shortcodeService.deleteShortcode(mockEmbed.id);
|
||||
expect(result).toEqualRight(true);
|
||||
});
|
||||
|
||||
test('should return SHORTCODE_NOT_FOUND error when Shortcode is invalid', async () => {
|
||||
mockPrisma.shortcode.delete.mockRejectedValue('RecordNotFound');
|
||||
|
||||
expect(shortcodeService.deleteShortcode('invalid')).resolves.toEqualLeft(
|
||||
SHORTCODE_NOT_FOUND,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('fetchAllShortcodes', () => {
|
||||
test('should return list of Shortcodes with valid inputs and no cursor', async () => {
|
||||
mockPrisma.shortcode.findMany.mockResolvedValueOnce(
|
||||
shortcodesWithUserEmail,
|
||||
);
|
||||
|
||||
const result = await shortcodeService.fetchAllShortcodes(
|
||||
{
|
||||
cursor: null,
|
||||
take: 10,
|
||||
},
|
||||
user.email,
|
||||
);
|
||||
expect(result).toEqual(<ShortcodeWithUserEmail[]>[
|
||||
{
|
||||
id: shortcodes[0].id,
|
||||
request: JSON.stringify(shortcodes[0].request),
|
||||
properties: JSON.stringify(shortcodes[0].embedProperties),
|
||||
createdOn: shortcodes[0].createdOn,
|
||||
creator: {
|
||||
uid: user.uid,
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: shortcodes[1].id,
|
||||
request: JSON.stringify(shortcodes[1].request),
|
||||
properties: JSON.stringify(shortcodes[1].embedProperties),
|
||||
createdOn: shortcodes[1].createdOn,
|
||||
creator: {
|
||||
uid: user.uid,
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return list of Shortcode with valid inputs and cursor', async () => {
|
||||
mockPrisma.shortcode.findMany.mockResolvedValue([
|
||||
shortcodesWithUserEmail[1],
|
||||
]);
|
||||
|
||||
const result = await shortcodeService.fetchAllShortcodes(
|
||||
{
|
||||
cursor: 'blablabla',
|
||||
take: 10,
|
||||
},
|
||||
user.email,
|
||||
);
|
||||
expect(result).toEqual(<ShortcodeWithUserEmail[]>[
|
||||
{
|
||||
id: shortcodes[1].id,
|
||||
request: JSON.stringify(shortcodes[1].request),
|
||||
properties: JSON.stringify(shortcodes[1].embedProperties),
|
||||
createdOn: shortcodes[1].createdOn,
|
||||
creator: {
|
||||
uid: user.uid,
|
||||
email: user.email,
|
||||
},
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
test('should return an empty array for an invalid cursor', async () => {
|
||||
mockPrisma.shortcode.findMany.mockResolvedValue([]);
|
||||
|
||||
const result = await shortcodeService.fetchAllShortcodes(
|
||||
{
|
||||
cursor: 'invalidcursor',
|
||||
take: 10,
|
||||
},
|
||||
user.email,
|
||||
);
|
||||
|
||||
expect(result).toHaveLength(0);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
SHORTCODE_PROPERTIES_NOT_FOUND,
|
||||
} from 'src/errors';
|
||||
import { UserDataHandler } from 'src/user/user.data.handler';
|
||||
import { Shortcode, ShortcodeWithUserEmail } from './shortcode.model';
|
||||
import { Shortcode } from './shortcode.model';
|
||||
import { Shortcode as DBShortCode } from '@prisma/client';
|
||||
import { PubSubService } from 'src/pubsub/pubsub.service';
|
||||
import { UserService } from 'src/user/user.service';
|
||||
@@ -180,7 +180,7 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a ShortCode created by User of uid
|
||||
* Delete a ShortCode
|
||||
*
|
||||
* @param shortcode ShortCode
|
||||
* @param uid User Uid
|
||||
@@ -223,26 +223,6 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
|
||||
return deletedShortCodes.count;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a Shortcode
|
||||
*
|
||||
* @param shortcodeID ID of Shortcode being deleted
|
||||
* @returns Boolean on successful deletion
|
||||
*/
|
||||
async deleteShortcode(shortcodeID: string) {
|
||||
try {
|
||||
await this.prisma.shortcode.delete({
|
||||
where: {
|
||||
id: shortcodeID,
|
||||
},
|
||||
});
|
||||
|
||||
return E.right(true);
|
||||
} catch (error) {
|
||||
return E.left(SHORTCODE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update a created Shortcode
|
||||
* @param shortcodeID Shortcode ID
|
||||
@@ -283,57 +263,4 @@ export class ShortcodeService implements UserDataHandler, OnModuleInit {
|
||||
return E.left(SHORTCODE_NOT_FOUND);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch all created ShortCodes
|
||||
*
|
||||
* @param args Pagination arguments
|
||||
* @param userEmail User email
|
||||
* @returns ShortcodeWithUserEmail
|
||||
*/
|
||||
async fetchAllShortcodes(
|
||||
args: PaginationArgs,
|
||||
userEmail: string | null = null,
|
||||
) {
|
||||
const shortCodes = await this.prisma.shortcode.findMany({
|
||||
where: userEmail
|
||||
? {
|
||||
User: {
|
||||
email: userEmail,
|
||||
},
|
||||
}
|
||||
: undefined,
|
||||
orderBy: {
|
||||
createdOn: 'desc',
|
||||
},
|
||||
skip: args.cursor ? 1 : 0,
|
||||
take: args.take,
|
||||
cursor: args.cursor ? { id: args.cursor } : undefined,
|
||||
include: {
|
||||
User: true,
|
||||
},
|
||||
});
|
||||
|
||||
const fetchedShortCodes: ShortcodeWithUserEmail[] = shortCodes.map(
|
||||
(code) => {
|
||||
return <ShortcodeWithUserEmail>{
|
||||
id: code.id,
|
||||
request: JSON.stringify(code.request),
|
||||
properties:
|
||||
code.embedProperties != null
|
||||
? JSON.stringify(code.embedProperties)
|
||||
: null,
|
||||
createdOn: code.createdOn,
|
||||
creator: code.User
|
||||
? {
|
||||
uid: code.User.uid,
|
||||
email: code.User.email,
|
||||
}
|
||||
: null,
|
||||
};
|
||||
},
|
||||
);
|
||||
|
||||
return fetchedShortCodes;
|
||||
}
|
||||
}
|
||||
|
||||
5
packages/hoppscotch-selfhost-web/aio.Caddyfile
Normal file
5
packages/hoppscotch-selfhost-web/aio.Caddyfile
Normal file
@@ -0,0 +1,5 @@
|
||||
:80 :3000 {
|
||||
try_files {path} /
|
||||
root * /site
|
||||
file_server
|
||||
}
|
||||
@@ -223,6 +223,8 @@ export default defineConfig({
|
||||
/twitter/,
|
||||
/github/,
|
||||
/announcements/,
|
||||
/admin/,
|
||||
/backend/,
|
||||
],
|
||||
},
|
||||
}),
|
||||
|
||||
@@ -1,6 +1,36 @@
|
||||
#!/usr/local/bin/node
|
||||
import { execSync } from "child_process"
|
||||
import { execSync, spawn } from "child_process"
|
||||
import fs from "fs"
|
||||
import process from "process"
|
||||
|
||||
function runChildProcessWithPrefix(command, args, prefix) {
|
||||
const childProcess = spawn(command, args);
|
||||
|
||||
childProcess.stdout.on('data', (data) => {
|
||||
const output = data.toString().trim().split('\n');
|
||||
output.forEach((line) => {
|
||||
console.log(`${prefix} | ${line}`);
|
||||
});
|
||||
});
|
||||
|
||||
childProcess.stderr.on('data', (data) => {
|
||||
const error = data.toString().trim().split('\n');
|
||||
error.forEach((line) => {
|
||||
console.error(`${prefix} | ${line}`);
|
||||
});
|
||||
});
|
||||
|
||||
childProcess.on('close', (code) => {
|
||||
console.log(`${prefix} Child process exited with code ${code}`);
|
||||
});
|
||||
|
||||
childProcess.on('error', (stuff) => {
|
||||
console.log("error")
|
||||
console.log(stuff)
|
||||
})
|
||||
|
||||
return childProcess
|
||||
}
|
||||
|
||||
const envFileContent = Object.entries(process.env)
|
||||
.filter(([env]) => env.startsWith("VITE_"))
|
||||
@@ -16,3 +46,19 @@ fs.writeFileSync("build.env", envFileContent)
|
||||
execSync(`npx import-meta-env -x build.env -e build.env -p "/site/**/*"`)
|
||||
|
||||
fs.rmSync("build.env")
|
||||
|
||||
const caddyFileName = process.env.ENABLE_SUBPATH_BASED_ACCESS === 'true' ? 'sh-admin-subpath-access.Caddyfile' : 'sh-admin-multiport-setup.Caddyfile'
|
||||
const caddyProcess = runChildProcessWithPrefix("caddy", ["run", "--config", `/etc/caddy/${caddyFileName}`, "--adapter", "caddyfile"], "App/Admin Dashboard Caddy")
|
||||
|
||||
caddyProcess.on("exit", (code) => {
|
||||
console.log(`Exiting process because Caddy Server exited with code ${code}`)
|
||||
process.exit(code)
|
||||
})
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log("SIGINT received, exiting...")
|
||||
|
||||
caddyProcess.kill("SIGINT")
|
||||
|
||||
process.exit(0)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
:80 :3100 {
|
||||
try_files {path} /
|
||||
root * /site/sh-admin-multiport-setup
|
||||
file_server
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
:80 :3100 {
|
||||
try_files {path} /admin*
|
||||
root * /site/sh-admin-subpath-access
|
||||
file_server
|
||||
}
|
||||
@@ -42,7 +42,7 @@ export const isLoadingInitialRoute = readonly(_isLoadingInitialRoute);
|
||||
export default <HoppModule>{
|
||||
onVueAppInit(app) {
|
||||
const router = createRouter({
|
||||
history: createWebHistory(),
|
||||
history: createWebHistory(import.meta.env.BASE_URL),
|
||||
routes,
|
||||
});
|
||||
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
:class="{ 'min-h-screen': statusCode !== 404 }"
|
||||
>
|
||||
<img
|
||||
:src="`/images/youre_lost.svg`"
|
||||
:src="imgUrl"
|
||||
loading="lazy"
|
||||
class="inline-flex flex-col object-contain object-center mb-2 h-46 w-46"
|
||||
:alt="message"
|
||||
@@ -44,6 +44,8 @@ const props = defineProps({
|
||||
},
|
||||
});
|
||||
|
||||
const imgUrl = `${import.meta.env.BASE_URL}images/youre_lost.svg`
|
||||
|
||||
const statusCode = computed(() => props.error?.statusCode ?? 404);
|
||||
|
||||
const message = computed(
|
||||
|
||||
@@ -13,16 +13,20 @@ RUN pnpm install -f --offline
|
||||
|
||||
|
||||
FROM base_builder as backend
|
||||
RUN apk add caddy
|
||||
WORKDIR /usr/src/app/packages/hoppscotch-backend
|
||||
RUN pnpm exec prisma generate
|
||||
RUN pnpm run build
|
||||
COPY --from=base_builder /usr/src/app/packages/hoppscotch-backend/backend-subpath.Caddyfile /etc/caddy/backend-subpath.Caddyfile
|
||||
COPY --from=base_builder /usr/src/app/packages/hoppscotch-backend/backend-multiport.Caddyfile /etc/caddy/backend-multiport.Caddyfile
|
||||
# Remove the env file to avoid backend copying it in and using it
|
||||
RUN rm "../../.env"
|
||||
ENV PRODUCTION="true"
|
||||
ENV PORT=3170
|
||||
ENV PORT=8080
|
||||
ENV APP_PORT=${PORT}
|
||||
ENV DB_URL=${DATABASE_URL}
|
||||
CMD ["pnpm", "run", "start:migrate:prod"]
|
||||
CMD ["node", "/usr/src/app/packages/hoppscotch-backend/prod_run.mjs"]
|
||||
EXPOSE 80
|
||||
EXPOSE 3170
|
||||
|
||||
FROM base_builder as fe_builder
|
||||
@@ -31,39 +35,51 @@ RUN pnpm run generate
|
||||
|
||||
FROM caddy:2-alpine as app
|
||||
WORKDIR /site
|
||||
COPY --from=fe_builder /usr/src/app/packages/hoppscotch-sh-admin/prod_run.mjs /usr
|
||||
COPY --from=fe_builder /usr/src/app/packages/hoppscotch-selfhost-web/Caddyfile /etc/caddy/Caddyfile
|
||||
COPY --from=fe_builder /usr/src/app/packages/hoppscotch-selfhost-web/prod_run.mjs /usr
|
||||
COPY --from=fe_builder /usr/src/app/packages/hoppscotch-selfhost-web/aio.Caddyfile /etc/caddy/Caddyfile
|
||||
COPY --from=fe_builder /usr/src/app/packages/hoppscotch-selfhost-web/dist/ .
|
||||
RUN apk add nodejs npm
|
||||
RUN npm install -g @import-meta-env/cli
|
||||
EXPOSE 8080
|
||||
EXPOSE 80
|
||||
EXPOSE 3000
|
||||
CMD ["/bin/sh", "-c", "node /usr/prod_run.mjs && caddy run --config /etc/caddy/Caddyfile --adapter caddyfile"]
|
||||
|
||||
FROM base_builder as sh_admin_builder
|
||||
WORKDIR /usr/src/app/packages/hoppscotch-sh-admin
|
||||
# Generate two builds for `sh-admin`, one based on subpath-access and the regular build
|
||||
RUN pnpm run build
|
||||
RUN pnpm run build --outDir dist-subpath-access --base /admin/
|
||||
|
||||
FROM caddy:2-alpine as sh_admin
|
||||
WORKDIR /site
|
||||
COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/prod_run.mjs /usr
|
||||
COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/Caddyfile /etc/caddy/Caddyfile
|
||||
COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/dist/ .
|
||||
COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/sh-admin-multiport-setup.Caddyfile /etc/caddy/sh-admin-multiport-setup.Caddyfile
|
||||
COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/sh-admin-subpath-access.Caddyfile /etc/caddy/sh-admin-subpath-access.Caddyfile
|
||||
COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/dist /site/sh-admin-multiport-setup
|
||||
COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/dist-subpath-access /site/sh-admin-subpath-access
|
||||
RUN apk add nodejs npm
|
||||
RUN npm install -g @import-meta-env/cli
|
||||
EXPOSE 8080
|
||||
CMD ["/bin/sh", "-c", "node /usr/prod_run.mjs && caddy run --config /etc/caddy/Caddyfile --adapter caddyfile"]
|
||||
EXPOSE 80
|
||||
EXPOSE 3100
|
||||
CMD ["node","/usr/prod_run.mjs"]
|
||||
|
||||
FROM backend as aio
|
||||
RUN apk add caddy tini
|
||||
RUN npm install -g @import-meta-env/cli
|
||||
COPY --from=fe_builder /usr/src/app/packages/hoppscotch-selfhost-web/dist /site/selfhost-web
|
||||
COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/dist /site/sh-admin
|
||||
COPY aio.Caddyfile /etc/caddy/Caddyfile
|
||||
COPY --from=sh_admin_builder /usr/src/app/packages/hoppscotch-sh-admin/dist-subpath-access /site/sh-admin-subpath-access
|
||||
COPY aio-multiport-setup.Caddyfile /etc/caddy/aio-multiport-setup.Caddyfile
|
||||
COPY aio-subpath-access.Caddyfile /etc/caddy/aio-subpath-access.Caddyfile
|
||||
ENTRYPOINT [ "tini", "--" ]
|
||||
RUN apk --no-cache add curl
|
||||
COPY --chmod=755 healthcheck.sh .
|
||||
HEALTHCHECK --interval=2s CMD /bin/sh ./healthcheck.sh
|
||||
CMD ["node", "/usr/src/app/aio_run.mjs"]
|
||||
EXPOSE 3170
|
||||
EXPOSE 8080
|
||||
EXPOSE 3000
|
||||
EXPOSE 3100
|
||||
EXPOSE 80
|
||||
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user