Compare commits

..

13 Commits

Author SHA1 Message Date
mirarifhasan
54a7e73981 chore: shortcode seed added and revamp the script 2024-05-08 21:17:43 +06:00
mirarifhasan
2a3dce6621 fix: generate fake email in lower case 2024-05-08 20:14:20 +06:00
mirarifhasan
260f526edc fix: dist folder being unsupported 2024-05-07 13:02:44 +06:00
mirarifhasan
20b9b94b53 build: seed file added
run nestjs app in dev mode, prod mode has an error
2024-05-07 11:30:32 +06:00
Andrew Bastin
391e5a20f5 chore: bump versions to 2024.3.3 2024-05-06 22:57:33 +05:30
Andrew Bastin
4b8f3bd8da fix: code generate modal erroring out (#4033) 2024-05-06 22:44:13 +05:30
Joel Jacob Stephen
94248076e6 refactor(sh-admin): improved handling of server configurations in admin dashboard (#3971)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-05-06 21:50:31 +05:30
Nivedin
eecc3db4e9 chore: update placeholder text (#4023) 2024-04-30 16:49:32 +05:30
Andrew Bastin
426e7594f4 fix: tab systems erroring out due to out of sync tabOrdering and tabMap 2024-04-29 20:32:27 +05:30
Andrew Bastin
934dc473f0 chore: bump version to 2024.3.2 2024-04-29 19:21:48 +05:30
Andrew Bastin
be57255bf7 refactor: update to dioc v3 (#4009)
Co-authored-by: jamesgeorge007 <jamesgeorge998001@gmail.com>
2024-04-29 19:06:18 +05:30
Balu Babu
f89561da54 fix: resolved mailer module email issue (#4000) 2024-04-29 12:05:07 +05:30
Joel Jacob Stephen
c2c4e620c2 fix(common): rest and graphql pages not being rendered when french is selected as app language (#4004)
Co-authored-by: nivedin <nivedinp@gmail.com>
2024-04-25 18:01:46 +05:30
75 changed files with 16955 additions and 18692 deletions

View File

@@ -38,7 +38,7 @@
}, },
"packageExtensions": { "packageExtensions": {
"httpsnippet@3.0.1": { "httpsnippet@3.0.1": {
"peerDependencies": { "dependencies": {
"ajv": "6.12.3" "ajv": "6.12.3"
} }
} }

View File

@@ -3,9 +3,7 @@
"collection": "@nestjs/schematics", "collection": "@nestjs/schematics",
"sourceRoot": "src", "sourceRoot": "src",
"compilerOptions": { "compilerOptions": {
"assets": [ "assets": [{ "include": "mailer/templates/**/*", "outDir": "dist" }],
"**/*.hbs"
],
"watchAssets": true "watchAssets": true
} }
} }

View File

@@ -1,10 +1,13 @@
{ {
"name": "hoppscotch-backend", "name": "hoppscotch-backend",
"version": "2024.3.1", "version": "2024.3.3",
"description": "", "description": "",
"author": "", "author": "",
"private": true, "private": true,
"license": "UNLICENSED", "license": "UNLICENSED",
"prisma": {
"seed": "ts-node prisma/seed.ts"
},
"scripts": { "scripts": {
"prebuild": "rimraf dist", "prebuild": "rimraf dist",
"build": "nest build", "build": "nest build",
@@ -66,6 +69,7 @@
"rxjs": "7.6.0" "rxjs": "7.6.0"
}, },
"devDependencies": { "devDependencies": {
"@faker-js/faker": "8.4.1",
"@nestjs/cli": "10.2.1", "@nestjs/cli": "10.2.1",
"@nestjs/schematics": "10.0.3", "@nestjs/schematics": "10.0.3",
"@nestjs/testing": "10.2.7", "@nestjs/testing": "10.2.7",

View File

@@ -0,0 +1,444 @@
import { faker } from '@faker-js/faker';
import { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
const createUsersAndAccounts = async (
entries = 10,
withDedicateUsers: { email: string; isAdmin: boolean }[] = [],
) => {
const createAUser = async (email?, isAdmin?) => {
try {
const newUser = await prisma.user.create({
data: {
displayName: faker.person.fullName(),
email: email ?? faker.internet.email().toLowerCase(),
photoURL: faker.image.avatar(),
isAdmin: isAdmin ?? false,
},
});
await prisma.account.create({
data: {
userId: newUser.uid,
provider: 'magic',
providerAccountId: newUser.email,
},
});
} catch (e) {
console.log('Error creating user/account:', e.message);
}
};
for (let i = 0; i < withDedicateUsers.length; i++) {
const user = withDedicateUsers[i];
await createAUser(user.email.toLowerCase(), user.isAdmin);
}
for (let i = 0; i < entries - withDedicateUsers.length; i++) {
await createAUser();
}
console.log('Users created');
};
const createInvitedUsers = async (entries = 10) => {
try {
const admin = await prisma.user.findFirst({ where: { isAdmin: true } });
for (let i = 0; i < entries; i++) {
await prisma.invitedUsers.create({
data: {
adminUid: admin.uid,
adminEmail: admin.email,
inviteeEmail: faker.internet.email().toLowerCase(),
},
});
}
console.log('Invited users created');
} catch (e) {
console.log('Error creating invited users:', e.message);
}
};
const createUserCollections = async (
parentCollEntries = 10,
childCollEntriesOnEachParent = 10,
) => {
const createACollection = async (userUid, parentID, orderIndex) => {
return prisma.userCollection.create({
data: {
parentID,
title: faker.vehicle.vehicle(),
orderIndex,
type: 'REST',
userUid,
},
});
};
try {
const users = await prisma.user.findMany();
for (let u = 0; u < users.length; u++) {
const user = users[u];
for (let i = 0; i < parentCollEntries; i++) {
const parentCollection = await createACollection(user.uid, null, i + 1);
for (let j = 0; j < childCollEntriesOnEachParent; j++) {
await createACollection(user.uid, parentCollection.id, j + 1);
}
}
}
console.log('User collections created');
} catch (e) {
console.log('Error creating user collections:', e.message);
}
};
const createUserRequests = async (entriesPerCollection = 10) => {
try {
const collections = await prisma.userCollection.findMany();
for (let i = 0; i < collections.length; i++) {
const collection = collections[i];
for (let j = 0; j < entriesPerCollection; j++) {
const requestTitle = faker.git.branch();
await prisma.userRequest.create({
data: {
collectionID: collection.id,
userUid: collection.userUid,
title: requestTitle,
request: {
v: '4',
auth: { authType: 'inherit', authActive: true },
body: { body: null, contentType: null },
name: requestTitle,
method: 'GET',
params: [],
headers: [],
endpoint: 'https://echo.hoppscotch.io',
testScript: '',
preRequestScript: '',
requestVariables: [],
},
type: collection.type,
orderIndex: j + 1,
},
});
}
}
console.log('User requests created');
} catch (e) {
console.log('Error creating user requests:', e.message);
}
};
const createUserEnvironments = async () => {
try {
const users = await prisma.user.findMany();
for (let i = 0; i < users.length; i++) {
const user = users[i];
const environments = await prisma.userEnvironment.findMany({
where: { userUid: user.uid },
});
if (environments.length > 1) continue; // skip if already created. (assuming default GLOBAL environments are created by APP itself)
await prisma.userEnvironment.createMany({
data: [
{
userUid: user.uid,
name: 'production',
variables: [
{
key: 'product_id',
value: faker.number.int({ max: 1000 }),
secret: false,
},
],
isGlobal: false,
},
{
userUid: user.uid,
name: 'development',
variables: [
{
key: 'product_id',
value: faker.number.int({ max: 1000 }),
secret: false,
},
],
isGlobal: false,
},
],
});
}
console.log('User environments created');
} catch (e) {
console.log('Error creating user environment:', e.message);
}
};
const createTeamsAndTeamMembers = async (
entries = 10,
memberCountInATeam = 4,
) => {
const createATeam = async (ownerUid) => {
return prisma.team.create({
data: {
name: faker.airline.airplane().name,
members: { create: { userUid: ownerUid, role: 'OWNER' } },
},
});
};
const createATeamMember = async (teamID, userUid) => {
try {
return prisma.teamMember.create({
data: {
teamID,
userUid,
role: +faker.number.binary() === 1 ? 'EDITOR' : 'VIEWER',
},
});
} catch (e) {
console.log(e.message);
}
};
try {
const users = await prisma.user.findMany();
for (let i = 0; i < entries; i++) {
const ownerIndex = faker.number.int({ min: 0, max: users.length - 1 });
const team = await createATeam(users[ownerIndex].uid); // create a team with owner
for (let j = 0; j < Math.min(memberCountInATeam, users.length) - 1; ) {
const memberIndex = faker.number.int({ min: 0, max: users.length - 1 });
// check if user already added
const existingTeamMember = await prisma.teamMember.findFirst({
where: {
teamID: team.id,
userUid: users[memberIndex].uid,
},
});
if (existingTeamMember) continue;
await createATeamMember(team.id, users[memberIndex].uid);
j++;
}
}
console.log('Teams and TeamMembers created');
} catch (e) {
console.log('Error creating teams and team members:', e.message);
}
};
const createTeamEnvironments = async () => {
try {
const teams = await prisma.team.findMany();
for (let i = 0; i < teams.length; i++) {
const team = teams[i];
const environments = await prisma.teamEnvironment.findMany({
where: { teamID: team.id },
});
if (environments.length > 1) continue; // skip if already created. (assuming default GLOBAL environments are created by APP itself)
await prisma.teamEnvironment.createMany({
data: [
{
teamID: team.id,
name: 'team_env_production',
variables: [
{
key: 'category_id',
value: faker.number.int({ max: 1000 }).toString(),
secret: false,
},
],
},
{
teamID: team.id,
name: 'team_env_development',
variables: [
{
key: 'category_id',
value: faker.number.int({ max: 1000 }).toString(),
secret: false,
},
],
},
],
});
}
console.log('Team environments created');
} catch (e) {
console.log('Error creating team environments: ', e.message);
}
};
const createTeamCollections = async (
parentCollEntries = 10,
childCollEntriesOnEachParent = 10,
) => {
const createACollection = async (teamID, parentID, orderIndex) => {
return prisma.teamCollection.create({
data: { parentID, title: faker.vehicle.vehicle(), orderIndex, teamID },
});
};
try {
const teams = await prisma.team.findMany();
for (let t = 0; t < teams.length; t++) {
const team = teams[t];
for (let i = 0; i < parentCollEntries; i++) {
const parentCollection = await createACollection(team.id, null, i + 1);
for (let j = 0; j < childCollEntriesOnEachParent; j++) {
await createACollection(team.id, parentCollection.id, j + 1);
}
}
}
console.log('Team collections created');
} catch (e) {
console.log('Error creating team collection:', e.message);
}
};
const createTeamRequests = async (entriesPerCollection = 10) => {
try {
const collections = await prisma.teamCollection.findMany();
for (let i = 0; i < collections.length; i++) {
const collection = collections[i];
for (let j = 0; j < entriesPerCollection; j++) {
const requestTitle = faker.git.branch();
await prisma.teamRequest.create({
data: {
collectionID: collection.id,
teamID: collection.teamID,
title: requestTitle,
request: {
v: '4',
auth: { authType: 'inherit', authActive: true },
body: { body: null, contentType: null },
name: requestTitle,
method: 'GET',
params: [],
headers: [],
endpoint: 'https://echo.hoppscotch.io',
testScript: '',
preRequestScript: '',
requestVariables: [],
},
orderIndex: j + 1,
},
});
}
}
console.log('Team requests created');
} catch (e) {
console.log('Error creating team requests:', e.message);
}
};
async function createShortcodes(entriesPerUser = 2) {
try {
const users = await prisma.user.findMany();
for (let i = 0; i < users.length; i++) {
const user = users[i];
for (let j = 0; j < entriesPerUser; j++) {
const userRequests = await prisma.userRequest.findMany({
where: { userUid: user.uid },
take: entriesPerUser,
});
let shortCodeRequestObj = {
v: '4',
id: userRequests[j].id,
auth: { authType: 'inherit', authActive: true },
body: { body: null, contentType: null },
name: 'driver-hack',
method: 'GET',
params: [],
headers: [],
endpoint: 'https://echo.hoppscotch.io',
testScript: '',
preRequestScript: '',
requestVariables: [],
};
await prisma.shortcode.create({
data: {
id: faker.string.alphanumeric(12),
creatorUid: user.uid,
request: shortCodeRequestObj,
embedProperties: null,
},
});
}
}
console.log('Shortcodes created');
} catch (e) {
console.log('Error creating shortcodes:', e.message);
}
}
async function clearAllData() {
try {
// Get all model names
const modelNames = Object.keys(prisma).filter(
(str) => !str.startsWith('_') && !str.startsWith('$'),
);
// Iterate through each model and delete all data
for (let i = 0; i < modelNames.length; i++) {
await prisma[modelNames[i]].deleteMany({});
}
console.log('All data cleared');
} catch (e) {
console.log('Error in clearing data:', e.message);
}
}
(async () => {
await clearAllData();
await createUsersAndAccounts(2, [
{ email: 'admin@gmail.com', isAdmin: true },
{ email: 'user@gmail.com', isAdmin: false },
]);
await createInvitedUsers(10);
// `userSettings` can be created by APP itself
await createUserCollections(10, 10);
await createUserRequests(10);
// `userHistory` can be created by APP itself
await createUserEnvironments();
await createShortcodes(3);
await createTeamsAndTeamMembers(10, 4);
// `teamInvitation` can be created by APP itself
await createTeamEnvironments();
await createTeamCollections(5, 5);
await createTeamRequests(3);
})();

View File

@@ -11,7 +11,6 @@ import {
EMAIL_FAILED, EMAIL_FAILED,
INVALID_EMAIL, INVALID_EMAIL,
ONLY_ONE_ADMIN_ACCOUNT, ONLY_ONE_ADMIN_ACCOUNT,
TEAM_INVALID_ID,
TEAM_INVITE_ALREADY_MEMBER, TEAM_INVITE_ALREADY_MEMBER,
TEAM_INVITE_NO_INVITE_FOUND, TEAM_INVITE_NO_INVITE_FOUND,
USERS_NOT_FOUND, USERS_NOT_FOUND,
@@ -319,11 +318,11 @@ export class AdminService {
const user = await this.userService.findUserByEmail(userEmail); const user = await this.userService.findUserByEmail(userEmail);
if (O.isNone(user)) return E.left(USER_NOT_FOUND); if (O.isNone(user)) return E.left(USER_NOT_FOUND);
const teamMember = await this.teamService.getTeamMember( const teamMember = await this.teamService.getTeamMemberTE(
teamID, teamID,
user.value.uid, user.value.uid,
); )();
if (!teamMember) { if (E.isLeft(teamMember)) {
const addedUser = await this.teamService.addMemberToTeamWithEmail( const addedUser = await this.teamService.addMemberToTeamWithEmail(
teamID, teamID,
userEmail, userEmail,
@@ -591,9 +590,9 @@ export class AdminService {
* @returns an Either of `Team` or error * @returns an Either of `Team` or error
*/ */
async getTeamInfo(teamID: string) { async getTeamInfo(teamID: string) {
const team = await this.teamService.getTeamWithID(teamID); const team = await this.teamService.getTeamWithIDTE(teamID)();
if (!team) return E.left(TEAM_INVALID_ID); if (E.isLeft(team)) return E.left(team.left);
return E.right(team); return E.right(team.right);
} }
/** /**

View File

@@ -15,11 +15,7 @@ import * as TE from 'fp-ts/TaskEither';
import * as E from 'fp-ts/Either'; import * as E from 'fp-ts/Either';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import { Team, TeamMember, TeamMemberRole } from 'src/team/team.model'; import { Team, TeamMember, TeamMemberRole } from 'src/team/team.model';
import { import { TEAM_INVITE_NO_INVITE_FOUND, USER_NOT_FOUND } from 'src/errors';
TEAM_INVALID_ID,
TEAM_INVITE_NO_INVITE_FOUND,
USER_NOT_FOUND,
} from 'src/errors';
import { GqlUser } from 'src/decorators/gql-user.decorator'; import { GqlUser } from 'src/decorators/gql-user.decorator';
import { User } from 'src/user/user.model'; import { User } from 'src/user/user.model';
import { UseGuards } from '@nestjs/common'; import { UseGuards } from '@nestjs/common';
@@ -54,9 +50,10 @@ export class TeamInvitationResolver {
description: 'Get the team associated to the invite', description: 'Get the team associated to the invite',
}) })
async team(@Parent() teamInvitation: TeamInvitation): Promise<Team> { async team(@Parent() teamInvitation: TeamInvitation): Promise<Team> {
const team = await this.teamService.getTeamWithID(teamInvitation.teamID); return pipe(
if (!team) throwErr(TEAM_INVALID_ID); this.teamService.getTeamWithIDTE(teamInvitation.teamID),
return team; TE.getOrElse(throwErr),
)();
} }
@ResolveField(() => User, { @ResolveField(() => User, {

View File

@@ -11,7 +11,6 @@ import {
} from '../errors'; } from '../errors';
import { mockDeep, mockReset } from 'jest-mock-extended'; import { mockDeep, mockReset } from 'jest-mock-extended';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import { skip } from 'rxjs';
const mockPrisma = mockDeep<PrismaService>(); const mockPrisma = mockDeep<PrismaService>();
@@ -756,8 +755,6 @@ describe('getMembersOfTeam', () => {
expect(mockPrisma.teamMember.findMany).toHaveBeenCalledWith({ expect(mockPrisma.teamMember.findMany).toHaveBeenCalledWith({
take: 10, take: 10,
skip: 0,
cursor: undefined,
where: { where: {
teamID: team.id, teamID: team.id,
}, },
@@ -809,8 +806,6 @@ describe('getTeamsOfUser', () => {
expect(mockPrisma.teamMember.findMany).toHaveBeenCalledWith({ expect(mockPrisma.teamMember.findMany).toHaveBeenCalledWith({
take: 10, take: 10,
skip: 0,
cursor: undefined,
where: { where: {
userUid: dbTeamMember.userUid, userUid: dbTeamMember.userUid,
}, },

View File

@@ -15,6 +15,7 @@ import {
} from '../errors'; } from '../errors';
import { PubSubService } from '../pubsub/pubsub.service'; import { PubSubService } from '../pubsub/pubsub.service';
import { flow, pipe } from 'fp-ts/function'; import { flow, pipe } from 'fp-ts/function';
import * as TE from 'fp-ts/TaskEither';
import * as TO from 'fp-ts/TaskOption'; import * as TO from 'fp-ts/TaskOption';
import * as O from 'fp-ts/Option'; import * as O from 'fp-ts/Option';
import * as E from 'fp-ts/Either'; import * as E from 'fp-ts/Either';
@@ -263,25 +264,37 @@ export class TeamService implements UserDataHandler, OnModuleInit {
} }
async getTeamsOfUser(uid: string, cursor: string | null): Promise<Team[]> { async getTeamsOfUser(uid: string, cursor: string | null): Promise<Team[]> {
const entries = await this.prisma.teamMember.findMany({ if (!cursor) {
take: 10, const entries = await this.prisma.teamMember.findMany({
skip: cursor ? 1 : 0, take: 10,
cursor: cursor where: {
? { userUid: uid,
teamID_userUid: { },
teamID: cursor, include: {
userUid: uid, team: true,
}, },
} });
: undefined,
where: { return entries.map((entry) => entry.team);
userUid: uid, } else {
}, const entries = await this.prisma.teamMember.findMany({
include: { take: 10,
team: true, skip: 1,
}, cursor: {
}); teamID_userUid: {
return entries.map((entry) => entry.team); teamID: cursor,
userUid: uid,
},
},
where: {
userUid: uid,
},
include: {
team: true,
},
});
return entries.map((entry) => entry.team);
}
} }
async getTeamWithID(teamID: string): Promise<Team | null> { async getTeamWithID(teamID: string): Promise<Team | null> {
@@ -298,6 +311,19 @@ export class TeamService implements UserDataHandler, OnModuleInit {
} }
} }
getTeamWithIDTE(teamID: string): TE.TaskEither<'team/invalid_id', Team> {
return pipe(
() => this.getTeamWithID(teamID),
TE.fromTask,
TE.chain(
TE.fromPredicate(
(x): x is Team => !!x,
() => TEAM_INVALID_ID,
),
),
);
}
/** /**
* Filters out team members that we weren't able to match * Filters out team members that we weren't able to match
* (also deletes the membership) * (also deletes the membership)
@@ -349,6 +375,19 @@ export class TeamService implements UserDataHandler, OnModuleInit {
} }
} }
getTeamMemberTE(teamID: string, userUid: string) {
return pipe(
() => this.getTeamMember(teamID, userUid),
TE.fromTask,
TE.chain(
TE.fromPredicate(
(x): x is TeamMember => !!x,
() => TEAM_MEMBER_NOT_FOUND,
),
),
);
}
async getRoleOfUserInTeam( async getRoleOfUserInTeam(
teamID: string, teamID: string,
userUid: string, userUid: string,
@@ -433,12 +472,25 @@ export class TeamService implements UserDataHandler, OnModuleInit {
): Promise<TeamMember[]> { ): Promise<TeamMember[]> {
let teamMembers: DbTeamMember[]; let teamMembers: DbTeamMember[];
teamMembers = await this.prisma.teamMember.findMany({ if (!cursor) {
take: 10, teamMembers = await this.prisma.teamMember.findMany({
skip: cursor ? 1 : 0, take: 10,
cursor: cursor ? { id: cursor } : undefined, where: {
where: { teamID }, teamID,
}); },
});
} else {
teamMembers = await this.prisma.teamMember.findMany({
take: 10,
skip: 1,
cursor: {
id: cursor,
},
where: {
teamID,
},
});
}
const members = teamMembers.map( const members = teamMembers.map(
(entry) => (entry) =>

View File

@@ -1,4 +1,4 @@
{ {
"extends": "./tsconfig.json", "extends": "./tsconfig.json",
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"] "exclude": ["node_modules", "test", "dist", "**/*spec.ts", "prisma/seed.ts"]
} }

View File

@@ -374,7 +374,8 @@
"mutations": "Mutations", "mutations": "Mutations",
"schema": "Schema", "schema": "Schema",
"subscriptions": "Subscriptions", "subscriptions": "Subscriptions",
"switch_connection": "Switch connection" "switch_connection": "Switch connection",
"url_placeholder": "Enter a GraphQL endpoint URL"
}, },
"graphql_collections": { "graphql_collections": {
"title": "GraphQL Collections" "title": "GraphQL Collections"
@@ -598,6 +599,7 @@
"title": "Request", "title": "Request",
"type": "Request type", "type": "Request type",
"url": "URL", "url": "URL",
"url_placeholder": "Enter a URL or paste a cURL command",
"variables": "Variables", "variables": "Variables",
"view_my_links": "View my links" "view_my_links": "View my links"
}, },

View File

@@ -58,24 +58,6 @@
"new": "Ajouter un nouveau", "new": "Ajouter un nouveau",
"star": "Ajouter une étoile" "star": "Ajouter une étoile"
}, },
"cookies": {
"modal": {
"new_domain_name": "Nouveau nom de domaine",
"set": "Définir un cookie",
"cookie_string": "Chaîne de caractères de cookie",
"enter_cookie_string": "Saisir la chaîne de caractères du cookie",
"cookie_name": "Nom",
"cookie_value": "Valeur",
"cookie_path": "Chemin d'accès",
"cookie_expires": "Expiration",
"managed_tab": "Gestion",
"raw_tab": "Brut",
"interceptor_no_support": "L'intercepteur que vous avez sélectionné ne prend pas en charge les cookies. Sélectionnez un autre intercepteur et réessayez.",
"empty_domains": "La liste des domaines est vide",
"empty_domain": "Le domaine est vide",
"no_cookies_in_domain": "Aucun cookie n'est défini pour ce domaine"
}
},
"app": { "app": {
"chat_with_us": "Discuter avec nous", "chat_with_us": "Discuter avec nous",
"contact_us": "Nous contacter", "contact_us": "Nous contacter",
@@ -187,7 +169,7 @@
}, },
"confirm": { "confirm": {
"close_unsaved_tab": "Êtes-vous sûr de vouloir fermer cet onglet ?", "close_unsaved_tab": "Êtes-vous sûr de vouloir fermer cet onglet ?",
"close_unsaved_tabs": "Êtes-vous sûr de vouloir fermer tous les onglets ? {Les onglets non enregistrés seront perdus.", "close_unsaved_tabs": "Êtes-vous sûr de vouloir fermer tous les onglets ? {count} onglets non enregistrés seront perdus",
"exit_team": "Êtes-vous sûr de vouloir quitter cette équipe ?", "exit_team": "Êtes-vous sûr de vouloir quitter cette équipe ?",
"logout": "Êtes-vous sûr de vouloir vous déconnecter?", "logout": "Êtes-vous sûr de vouloir vous déconnecter?",
"remove_collection": "Voulez-vous vraiment supprimer définitivement cette collection ?", "remove_collection": "Voulez-vous vraiment supprimer définitivement cette collection ?",
@@ -207,6 +189,24 @@
"open_request_in_new_tab": "Ouvrir la demande dans un nouvel onglet", "open_request_in_new_tab": "Ouvrir la demande dans un nouvel onglet",
"set_environment_variable": "Définir comme variable" "set_environment_variable": "Définir comme variable"
}, },
"cookies": {
"modal": {
"new_domain_name": "Nouveau nom de domaine",
"set": "Définir un cookie",
"cookie_string": "Chaîne de caractères de cookie",
"enter_cookie_string": "Saisir la chaîne de caractères du cookie",
"cookie_name": "Nom",
"cookie_value": "Valeur",
"cookie_path": "Chemin d'accès",
"cookie_expires": "Expiration",
"managed_tab": "Gestion",
"raw_tab": "Brut",
"interceptor_no_support": "L'intercepteur que vous avez sélectionné ne prend pas en charge les cookies. Sélectionnez un autre intercepteur et réessayez.",
"empty_domains": "La liste des domaines est vide",
"empty_domain": "Le domaine est vide",
"no_cookies_in_domain": "Aucun cookie n'est défini pour ce domaine"
}
},
"count": { "count": {
"header": "En-tête {count}", "header": "En-tête {count}",
"message": "Message {compte}", "message": "Message {compte}",
@@ -410,7 +410,7 @@
"description": "Inspecter les erreurs possibles", "description": "Inspecter les erreurs possibles",
"environment": { "environment": {
"add_environment": "Ajouter à l'environnement", "add_environment": "Ajouter à l'environnement",
"not_found": "La variable d'environnement “{environnement}“ n'a pas été trouvée." "not_found": "La variable d'environnement “{environment}“ n'a pas été trouvée."
}, },
"header": { "header": {
"cookie": "Le navigateur ne permet pas à Hoppscotch de définir l'en-tête Cookie. Pendant que nous travaillons sur l'application de bureau Hoppscotch (bientôt disponible), veuillez utiliser l'en-tête d'autorisation à la place." "cookie": "Le navigateur ne permet pas à Hoppscotch de définir l'en-tête Cookie. Pendant que nous travaillons sur l'application de bureau Hoppscotch (bientôt disponible), veuillez utiliser l'en-tête d'autorisation à la place."

View File

@@ -1,7 +1,7 @@
{ {
"name": "@hoppscotch/common", "name": "@hoppscotch/common",
"private": true, "private": true,
"version": "2024.3.1", "version": "2024.3.3",
"scripts": { "scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*", "dev": "pnpm exec npm-run-all -p -l dev:*",
"test": "vitest --run", "test": "vitest --run",
@@ -50,7 +50,7 @@
"axios": "1.6.2", "axios": "1.6.2",
"buffer": "6.0.3", "buffer": "6.0.3",
"cookie-es": "1.0.0", "cookie-es": "1.0.0",
"dioc": "1.0.1", "dioc": "3.0.1",
"esprima": "4.0.1", "esprima": "4.0.1",
"events": "3.3.0", "events": "3.3.0",
"fp-ts": "2.16.1", "fp-ts": "2.16.1",

View File

@@ -32,6 +32,7 @@ declare module 'vue' {
AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default'] AppSpotlightEntryRESTHistory: typeof import('./components/app/spotlight/entry/RESTHistory.vue')['default']
AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default'] AppSpotlightEntryRESTRequest: typeof import('./components/app/spotlight/entry/RESTRequest.vue')['default']
AppSpotlightEntryRESTTeamRequestEntry: typeof import('./components/app/spotlight/entry/RESTTeamRequestEntry.vue')['default'] AppSpotlightEntryRESTTeamRequestEntry: typeof import('./components/app/spotlight/entry/RESTTeamRequestEntry.vue')['default']
AppSpotlightSearch: typeof import('./components/app/SpotlightSearch.vue')['default']
AppSupport: typeof import('./components/app/Support.vue')['default'] AppSupport: typeof import('./components/app/Support.vue')['default']
Collections: typeof import('./components/collections/index.vue')['default'] Collections: typeof import('./components/collections/index.vue')['default']
CollectionsAdd: typeof import('./components/collections/Add.vue')['default'] CollectionsAdd: typeof import('./components/collections/Add.vue')['default']

View File

@@ -54,9 +54,7 @@
:key="tab.id" :key="tab.id"
:label="tab.label" :label="tab.label"
> >
<div <div class="divide-y divide-dividerLight">
class="divide-y divide-dividerLight rounded border border-divider"
>
<HoppSmartPlaceholder <HoppSmartPlaceholder
v-if="tab.variables.length === 0" v-if="tab.variables.length === 0"
:src="`/images/states/${colorMode.value}/blockchain.svg`" :src="`/images/states/${colorMode.value}/blockchain.svg`"

View File

@@ -56,9 +56,7 @@
:key="tab.id" :key="tab.id"
:label="tab.label" :label="tab.label"
> >
<div <div class="divide-y divide-dividerLight">
class="divide-y divide-dividerLight rounded border border-divider"
>
<HoppSmartPlaceholder <HoppSmartPlaceholder
v-if="tab.variables.length === 0" v-if="tab.variables.length === 0"
:src="`/images/states/${colorMode.value}/blockchain.svg`" :src="`/images/states/${colorMode.value}/blockchain.svg`"

View File

@@ -10,7 +10,7 @@
autocomplete="off" autocomplete="off"
spellcheck="false" spellcheck="false"
class="w-full rounded border border-divider bg-primaryLight px-4 py-2 text-secondaryDark" class="w-full rounded border border-divider bg-primaryLight px-4 py-2 text-secondaryDark"
:placeholder="`${t('request.url')}`" :placeholder="`${t('graphql.url_placeholder')}`"
:disabled="connected" :disabled="connected"
@keyup.enter="onConnectClick" @keyup.enter="onConnectClick"
/> />

View File

@@ -54,7 +54,7 @@
> >
<SmartEnvInput <SmartEnvInput
v-model="tab.document.request.endpoint" v-model="tab.document.request.endpoint"
:placeholder="`${t('request.url')}`" :placeholder="`${t('request.url_placeholder')}`"
:auto-complete-source="userHistories" :auto-complete-source="userHistories"
:auto-complete-env="true" :auto-complete-env="true"
:inspection-results="tabResults" :inspection-results="tabResults"

View File

@@ -201,7 +201,7 @@ export class TeamSearchService extends Service {
expandingCollections: Ref<string[]> = ref([]) expandingCollections: Ref<string[]> = ref([])
expandedCollections: Ref<string[]> = ref([]) expandedCollections: Ref<string[]> = ref([])
// FUTURE-TODO: ideally this should return the search results / formatted results instead of directly manipulating the result set // TODO: ideally this should return the search results / formatted results instead of directly manipulating the result set
// eg: do the spotlight formatting in the spotlight searcher and not here // eg: do the spotlight formatting in the spotlight searcher and not here
searchTeams = async (query: string, teamID: string) => { searchTeams = async (query: string, teamID: string) => {
if (!query.length) { if (!query.length) {

View File

@@ -1,5 +1,5 @@
import { HoppModule } from "." import { HoppModule } from "."
import { Container, Service } from "dioc" import { Container, ServiceClassInstance } from "dioc"
import { diocPlugin } from "dioc/vue" import { diocPlugin } from "dioc/vue"
import { DebugService } from "~/services/debug.service" import { DebugService } from "~/services/debug.service"
import { platform } from "~/platform" import { platform } from "~/platform"
@@ -22,7 +22,7 @@ if (import.meta.env.DEV) {
* services. Please use `useService` if within components or try to convert your * services. Please use `useService` if within components or try to convert your
* legacy subsystem into a service if possible. * legacy subsystem into a service if possible.
*/ */
export function getService<T extends typeof Service<any> & { ID: string }>( export function getService<T extends ServiceClassInstance<any>>(
service: T service: T
): InstanceType<T> { ): InstanceType<T> {
return serviceContainer.bind(service) return serviceContainer.bind(service)
@@ -30,11 +30,10 @@ export function getService<T extends typeof Service<any> & { ID: string }>(
export default <HoppModule>{ export default <HoppModule>{
onVueAppInit(app) { onVueAppInit(app) {
// TODO: look into this
// @ts-expect-error Something weird with Vue versions
app.use(diocPlugin, { app.use(diocPlugin, {
container: serviceContainer, container: serviceContainer,
}) })
for (const service of platform.addedServices ?? []) { for (const service of platform.addedServices ?? []) {
serviceContainer.bind(service) serviceContainer.bind(service)
} }

View File

@@ -8,14 +8,14 @@ import { AnalyticsPlatformDef } from "./analytics"
import { InterceptorsPlatformDef } from "./interceptors" import { InterceptorsPlatformDef } from "./interceptors"
import { HoppModule } from "~/modules" import { HoppModule } from "~/modules"
import { InspectorsPlatformDef } from "./inspectors" import { InspectorsPlatformDef } from "./inspectors"
import { Service } from "dioc" import { ServiceClassInstance } from "dioc"
import { IOPlatformDef } from "./io" import { IOPlatformDef } from "./io"
import { SpotlightPlatformDef } from "./spotlight" import { SpotlightPlatformDef } from "./spotlight"
export type PlatformDef = { export type PlatformDef = {
ui?: UIPlatformDef ui?: UIPlatformDef
addedHoppModules?: HoppModule[] addedHoppModules?: HoppModule[]
addedServices?: Array<typeof Service<unknown> & { ID: string }> addedServices?: Array<ServiceClassInstance<unknown>>
auth: AuthPlatformDef auth: AuthPlatformDef
analytics?: AnalyticsPlatformDef analytics?: AnalyticsPlatformDef
io: IOPlatformDef io: IOPlatformDef

View File

@@ -1,4 +1,4 @@
import { Service } from "dioc" import { Container, ServiceClassInstance } from "dioc"
import { Inspector } from "~/services/inspection" import { Inspector } from "~/services/inspection"
/** /**
@@ -8,8 +8,9 @@ export type PlatformInspectorsDef = {
// We are keeping this as the only mode for now // We are keeping this as the only mode for now
// So that if we choose to add other modes, we can do without breaking // So that if we choose to add other modes, we can do without breaking
type: "service" type: "service"
service: typeof Service<unknown> & { ID: string } & { // TODO: I don't think this type is effective, we have to come up with a better impl
new (): Service & Inspector service: ServiceClassInstance<unknown> & {
new (c: Container): Inspector
} }
} }

View File

@@ -1,12 +1,13 @@
import { Service } from "dioc" import { Container, ServiceClassInstance } from "dioc"
import { Interceptor } from "~/services/interceptor.service" import { Interceptor } from "~/services/interceptor.service"
export type PlatformInterceptorDef = export type PlatformInterceptorDef =
| { type: "standalone"; interceptor: Interceptor } | { type: "standalone"; interceptor: Interceptor }
| { | {
type: "service" type: "service"
service: typeof Service<unknown> & { ID: string } & { // TODO: I don't think this type is effective, we have to come up with a better impl
new (): Service & Interceptor service: ServiceClassInstance<unknown> & {
new (c: Container): Interceptor
} }
} }

View File

@@ -1,10 +1,10 @@
import { Service } from "dioc" import { Container, ServiceClassInstance } from "dioc"
import { SpotlightSearcher } from "~/services/spotlight" import { SpotlightSearcher } from "~/services/spotlight"
export type SpotlightPlatformDef = { export type SpotlightPlatformDef = {
additionalSearchers?: Array< additionalSearchers?: Array<
typeof Service<unknown> & { ID: string } & { ServiceClassInstance<unknown> & {
new (): Service & SpotlightSearcher new (c: Container): SpotlightSearcher
} }
> >
} }

View File

@@ -31,9 +31,7 @@ export class ExtensionInspectorService extends Service implements Inspector {
private readonly inspection = this.bind(InspectionService) private readonly inspection = this.bind(InspectionService)
constructor() { override onServiceInit() {
super()
this.inspection.registerInspector(this) this.inspection.registerInspector(this)
} }

View File

@@ -133,9 +133,7 @@ export class ExtensionInterceptorService
public selectable = { type: "selectable" as const } public selectable = { type: "selectable" as const }
constructor() { override onServiceInit() {
super()
this.listenForExtensionStatus() this.listenForExtensionStatus()
} }

View File

@@ -24,9 +24,7 @@ export class EnvironmentMenuService extends Service implements ContextMenu {
private readonly contextMenu = this.bind(ContextMenuService) private readonly contextMenu = this.bind(ContextMenuService)
constructor() { override onServiceInit() {
super()
this.contextMenu.registerMenu(this) this.contextMenu.registerMenu(this)
} }

View File

@@ -41,9 +41,7 @@ export class ParameterMenuService extends Service implements ContextMenu {
private readonly contextMenu = this.bind(ContextMenuService) private readonly contextMenu = this.bind(ContextMenuService)
constructor() { override onServiceInit() {
super()
this.contextMenu.registerMenu(this) this.contextMenu.registerMenu(this)
} }

View File

@@ -39,9 +39,7 @@ export class URLMenuService extends Service implements ContextMenu {
private readonly contextMenu = this.bind(ContextMenuService) private readonly contextMenu = this.bind(ContextMenuService)
private readonly restTab = this.bind(RESTTabService) private readonly restTab = this.bind(RESTTabService)
constructor() { override onServiceInit() {
super()
this.contextMenu.registerMenu(this) this.contextMenu.registerMenu(this)
} }

View File

@@ -20,10 +20,6 @@ export class CookieJarService extends Service {
*/ */
public cookieJar = ref(new Map<string, string[]>()) public cookieJar = ref(new Map<string, string[]>())
constructor() {
super()
}
public parseSetCookieString(setCookieString: string) { public parseSetCookieString(setCookieString: string) {
return setCookieParse(setCookieString) return setCookieParse(setCookieString)
} }

View File

@@ -14,9 +14,7 @@ import { Service } from "dioc"
export class DebugService extends Service { export class DebugService extends Service {
public static readonly ID = "DEBUG_SERVICE" public static readonly ID = "DEBUG_SERVICE"
constructor() { override onServiceInit() {
super()
console.log("DebugService is initialized...") console.log("DebugService is initialized...")
const container = this.getContainer() const container = this.getContainer()

View File

@@ -107,9 +107,7 @@ export class InspectionService extends Service {
private readonly restTab = this.bind(RESTTabService) private readonly restTab = this.bind(RESTTabService)
constructor() { override onServiceInit() {
super()
this.initializeListeners() this.initializeListeners()
} }

View File

@@ -53,9 +53,7 @@ export class EnvironmentInspectorService extends Service implements Inspector {
} }
)[0] )[0]
constructor() { override onServiceInit() {
super()
this.inspection.registerInspector(this) this.inspection.registerInspector(this)
} }

View File

@@ -22,9 +22,7 @@ export class HeaderInspectorService extends Service implements Inspector {
private readonly inspection = this.bind(InspectionService) private readonly inspection = this.bind(InspectionService)
private readonly interceptorService = this.bind(InterceptorService) private readonly interceptorService = this.bind(InterceptorService)
constructor() { override onServiceInit() {
super()
this.inspection.registerInspector(this) this.inspection.registerInspector(this)
} }

View File

@@ -23,9 +23,7 @@ export class ResponseInspectorService extends Service implements Inspector {
private readonly inspection = this.bind(InspectionService) private readonly inspection = this.bind(InspectionService)
constructor() { override onServiceInit() {
super()
this.inspection.registerInspector(this) this.inspection.registerInspector(this)
} }

View File

@@ -178,9 +178,7 @@ export class InterceptorService extends Service {
return this.interceptors.get(this.currentInterceptorID.value) return this.interceptors.get(this.currentInterceptorID.value)
}) })
constructor() { override onServiceInit() {
super()
// If the current interceptor is unselectable, select the first selectable one, else null // If the current interceptor is unselectable, select the first selectable one, else null
watch([() => this.interceptors, this.currentInterceptorID], () => { watch([() => this.interceptors, this.currentInterceptorID], () => {
if (!this.currentInterceptorID.value) return if (!this.currentInterceptorID.value) return

View File

@@ -109,10 +109,6 @@ export class OauthAuthService extends Service {
public static readonly ID = "OAUTH_AUTH_SERVICE" public static readonly ID = "OAUTH_AUTH_SERVICE"
static redirectURI = `${window.location.origin}/oauth` static redirectURI = `${window.location.origin}/oauth`
constructor() {
super()
}
} }
export const generateRandomString = () => { export const generateRandomString = () => {

View File

@@ -89,10 +89,6 @@ export class PersistenceService extends Service {
public hoppLocalConfigStorage: StorageLike = localStorage public hoppLocalConfigStorage: StorageLike = localStorage
constructor() {
super()
}
private showErrorToast(localStorageKey: string) { private showErrorToast(localStorageKey: string) {
const toast = useToast() const toast = useToast()
toast.error( toast.error(

View File

@@ -27,10 +27,6 @@ export class SecretEnvironmentService extends Service {
*/ */
public secretEnvironments = reactive(new Map<string, SecretVariable[]>()) public secretEnvironments = reactive(new Map<string, SecretVariable[]>())
constructor() {
super()
}
/** /**
* Add a new secret environment. * Add a new secret environment.
* @param id ID of the environment * @param id ID of the environment

View File

@@ -6,6 +6,7 @@ import {
import { nextTick, reactive, ref } from "vue" import { nextTick, reactive, ref } from "vue"
import { SpotlightSearcherResult } from "../../.." import { SpotlightSearcherResult } from "../../.."
import { TestContainer } from "dioc/testing" import { TestContainer } from "dioc/testing"
import { Container } from "dioc"
async function flushPromises() { async function flushPromises() {
return await new Promise((r) => setTimeout(r)) return await new Promise((r) => setTimeout(r))
@@ -32,12 +33,15 @@ describe("StaticSpotlightSearcherService", () => {
}, },
}) })
constructor() { // TODO: dioc > v3 does not recommend using constructors, move to onServiceInit
super({ constructor(c: Container) {
super(c, {
searchFields: ["text"], searchFields: ["text"],
fieldWeights: {}, fieldWeights: {},
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
} }
@@ -94,12 +98,15 @@ describe("StaticSpotlightSearcherService", () => {
}, },
}) })
constructor() { // TODO: dioc > v3 does not recommend using constructors, move to onServiceInit
super({ constructor(c: Container) {
super(c, {
searchFields: ["text"], searchFields: ["text"],
fieldWeights: {}, fieldWeights: {},
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
} }
@@ -159,12 +166,15 @@ describe("StaticSpotlightSearcherService", () => {
}, },
}) })
constructor() { // TODO: dioc > v3 does not recommend using constructors, move to onServiceInit
super({ constructor(c: Container) {
super(c, {
searchFields: ["text"], searchFields: ["text"],
fieldWeights: {}, fieldWeights: {},
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
} }
@@ -224,12 +234,15 @@ describe("StaticSpotlightSearcherService", () => {
}, },
}) })
constructor() { // TODO: dioc > v3 does not recommend using constructors, move to onServiceInit
super({ constructor(c: Container) {
super(c, {
searchFields: ["text"], searchFields: ["text"],
fieldWeights: {}, fieldWeights: {},
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
} }
@@ -285,12 +298,15 @@ describe("StaticSpotlightSearcherService", () => {
}, },
}) })
constructor() { // TODO: dioc > v3 does not recommend using constructors, move to onServiceInit
super({ constructor(c: Container) {
super(c, {
searchFields: ["text"], searchFields: ["text"],
fieldWeights: {}, fieldWeights: {},
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
} }
@@ -354,12 +370,15 @@ describe("StaticSpotlightSearcherService", () => {
}, },
}) })
constructor() { // TODO: dioc > v3 does not recommend using constructors, move to onServiceInit
super({ constructor(c: Container) {
super(c, {
searchFields: ["text", "alternate"], searchFields: ["text", "alternate"],
fieldWeights: {}, fieldWeights: {},
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
} }

View File

@@ -1,4 +1,4 @@
import { Service } from "dioc" import { Container, Service } from "dioc"
import { import {
type SpotlightSearcher, type SpotlightSearcher,
type SpotlightSearcherResult, type SpotlightSearcherResult,
@@ -67,8 +67,12 @@ export abstract class StaticSpotlightSearcherService<
private _documents: Record<string, Doc> = {} private _documents: Record<string, Doc> = {}
constructor(private opts: StaticSpotlightSearcherOptions<Doc>) { // TODO: This pattern is no longer recommended in dioc > 3, move to something else
super() constructor(
c: Container,
private opts: StaticSpotlightSearcherOptions<Doc>
) {
super(c)
this.minisearch = new MiniSearch({ this.minisearch = new MiniSearch({
fields: opts.searchFields as string[], fields: opts.searchFields as string[],

View File

@@ -50,9 +50,7 @@ export class CollectionsSpotlightSearcherService
private readonly spotlight = this.bind(SpotlightService) private readonly spotlight = this.bind(SpotlightService)
private readonly workspaceService = this.bind(WorkspaceService) private readonly workspaceService = this.bind(WorkspaceService)
constructor() { override onServiceInit() {
super()
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -26,7 +26,7 @@ import IconEdit from "~icons/lucide/edit"
import IconLayers from "~icons/lucide/layers" import IconLayers from "~icons/lucide/layers"
import IconTrash2 from "~icons/lucide/trash-2" import IconTrash2 from "~icons/lucide/trash-2"
import { Service } from "dioc" import { Container, Service } from "dioc"
import * as TE from "fp-ts/TaskEither" import * as TE from "fp-ts/TaskEither"
import { pipe } from "fp-ts/function" import { pipe } from "fp-ts/function"
import { cloneDeep } from "lodash-es" import { cloneDeep } from "lodash-es"
@@ -164,15 +164,18 @@ export class EnvironmentsSpotlightSearcherService extends StaticSpotlightSearche
}, },
}) })
constructor() { // TODO: This pattern is no longer recommended in dioc > 3, move to something else
super({ constructor(c: Container) {
super(c, {
searchFields: ["text", "alternates"], searchFields: ["text", "alternates"],
fieldWeights: { fieldWeights: {
text: 2, text: 2,
alternates: 1, alternates: 1,
}, },
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }
@@ -277,9 +280,7 @@ export class SwitchEnvSpotlightSearcherService
private readonly workspaceService = this.bind(WorkspaceService) private readonly workspaceService = this.bind(WorkspaceService)
private teamEnvironmentList: TeamEnvironment[] = [] private teamEnvironmentList: TeamEnvironment[] = []
constructor() { override onServiceInit() {
super()
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -15,6 +15,7 @@ import IconBook from "~icons/lucide/book"
import IconLifeBuoy from "~icons/lucide/life-buoy" import IconLifeBuoy from "~icons/lucide/life-buoy"
import IconZap from "~icons/lucide/zap" import IconZap from "~icons/lucide/zap"
import { platform } from "~/platform" import { platform } from "~/platform"
import { Container } from "dioc"
type Doc = { type Doc = {
text: string | string[] text: string | string[]
@@ -89,15 +90,18 @@ export class GeneralSpotlightSearcherService extends StaticSpotlightSearcherServ
}, },
}) })
constructor() { // TODO: This is not recommended as of dioc > 3. Move to onServiceInit instead
super({ constructor(c: Container) {
super(c, {
searchFields: ["text", "alternates"], searchFields: ["text", "alternates"],
fieldWeights: { fieldWeights: {
text: 2, text: 2,
alternates: 1, alternates: 1,
}, },
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -66,9 +66,7 @@ export class HistorySpotlightSearcherService
} }
)[0] )[0]
constructor() { override onServiceInit() {
super()
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -31,9 +31,7 @@ export class InterceptorSpotlightSearcherService
private readonly spotlight = this.bind(SpotlightService) private readonly spotlight = this.bind(SpotlightService)
private interceptorService = this.bind(InterceptorService) private interceptorService = this.bind(InterceptorService)
constructor() { override onServiceInit() {
super()
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -8,6 +8,7 @@ import {
} from "./base/static.searcher" } from "./base/static.searcher"
import IconShare from "~icons/lucide/share" import IconShare from "~icons/lucide/share"
import { Container } from "dioc"
type Doc = { type Doc = {
text: string text: string
@@ -39,15 +40,18 @@ export class MiscellaneousSpotlightSearcherService extends StaticSpotlightSearch
}, },
}) })
constructor() { // TODO: Constructors are no longer recommended as of dioc > 3, move to onServiceInit
super({ constructor(c: Container) {
super(c, {
searchFields: ["text", "alternates"], searchFields: ["text", "alternates"],
fieldWeights: { fieldWeights: {
text: 2, text: 2,
alternates: 1, alternates: 1,
}, },
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -8,6 +8,7 @@ import {
} from "./base/static.searcher" } from "./base/static.searcher"
import IconArrowRight from "~icons/lucide/arrow-right" import IconArrowRight from "~icons/lucide/arrow-right"
import { Container } from "dioc"
type Doc = { type Doc = {
text: string text: string
@@ -61,15 +62,18 @@ export class NavigationSpotlightSearcherService extends StaticSpotlightSearcherS
private docKeys = Object.keys(this.documents) private docKeys = Object.keys(this.documents)
constructor() { // TODO: Constructors are no longer recommended as of dioc > 3, use onServiceInit instead
super({ constructor(c: Container) {
super(c, {
searchFields: ["text", "alternates"], searchFields: ["text", "alternates"],
fieldWeights: { fieldWeights: {
text: 2, text: 2,
alternates: 1, alternates: 1,
}, },
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -20,6 +20,7 @@ import IconRotateCCW from "~icons/lucide/rotate-ccw"
import IconSave from "~icons/lucide/save" import IconSave from "~icons/lucide/save"
import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue" import { GQLOptionTabs } from "~/components/graphql/RequestOptions.vue"
import { RESTTabService } from "~/services/tab/rest" import { RESTTabService } from "~/services/tab/rest"
import { Container } from "dioc"
type Doc = { type Doc = {
text: string | string[] text: string | string[]
@@ -224,15 +225,18 @@ export class RequestSpotlightSearcherService extends StaticSpotlightSearcherServ
}, },
}) })
constructor() { // TODO: Constructors are no longer recommended as of dioc > 3, use onServiceInit instead
super({ constructor(c: Container) {
super(c, {
searchFields: ["text", "alternates"], searchFields: ["text", "alternates"],
fieldWeights: { fieldWeights: {
text: 2, text: 2,
alternates: 1, alternates: 1,
}, },
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -9,6 +9,7 @@ import {
import IconDownload from "~icons/lucide/download" import IconDownload from "~icons/lucide/download"
import IconCopy from "~icons/lucide/copy" import IconCopy from "~icons/lucide/copy"
import { Container } from "dioc"
type Doc = { type Doc = {
text: string text: string
@@ -56,15 +57,18 @@ export class ResponseSpotlightSearcherService extends StaticSpotlightSearcherSer
}, },
}) })
constructor() { // TODO: Constructors are no longer recommended as of dioc > 3, move to onServiceInit
super({ constructor(c: Container) {
super(c, {
searchFields: ["text", "alternates"], searchFields: ["text", "alternates"],
fieldWeights: { fieldWeights: {
text: 2, text: 2,
alternates: 1, alternates: 1,
}, },
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -15,6 +15,7 @@ import IconMonitor from "~icons/lucide/monitor"
import IconMoon from "~icons/lucide/moon" import IconMoon from "~icons/lucide/moon"
import IconSun from "~icons/lucide/sun" import IconSun from "~icons/lucide/sun"
import IconCheckCircle from "~icons/lucide/check-circle" import IconCheckCircle from "~icons/lucide/check-circle"
import { Container } from "dioc"
type Doc = { type Doc = {
text: string | string[] text: string | string[]
@@ -100,15 +101,18 @@ export class SettingsSpotlightSearcherService extends StaticSpotlightSearcherSer
}, },
}) })
constructor() { // TODO: Constuctors are no longer recommended as of dioc > 3, move to onServiceInit
super({ constructor(c: Container) {
super(c, {
searchFields: ["text", "alternates"], searchFields: ["text", "alternates"],
fieldWeights: { fieldWeights: {
text: 2, text: 2,
alternates: 1, alternates: 1,
}, },
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -14,6 +14,7 @@ import IconXSquare from "~icons/lucide/x-square"
import { invokeAction } from "~/helpers/actions" import { invokeAction } from "~/helpers/actions"
import { RESTTabService } from "~/services/tab/rest" import { RESTTabService } from "~/services/tab/rest"
import { GQLTabService } from "~/services/tab/graphql" import { GQLTabService } from "~/services/tab/graphql"
import { Container } from "dioc"
type Doc = { type Doc = {
text: string | string[] text: string | string[]
@@ -89,15 +90,18 @@ export class TabSpotlightSearcherService extends StaticSpotlightSearcherService<
}, },
}) })
constructor() { // TODO: Constructors are no longer recommended as of dioc > 3, use onServiceInit instead
super({ constructor(c: Container) {
super(c, {
searchFields: ["text", "alternates"], searchFields: ["text", "alternates"],
fieldWeights: { fieldWeights: {
text: 2, text: 2,
alternates: 1, alternates: 1,
}, },
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -39,9 +39,7 @@ export class TeamsSpotlightSearcherService
private readonly tabs = this.bind(RESTTabService) private readonly tabs = this.bind(RESTTabService)
constructor() { override onServiceInit() {
super()
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -9,6 +9,7 @@ import { useStreamStatic } from "~/composables/stream"
import IconLogin from "~icons/lucide/log-in" import IconLogin from "~icons/lucide/log-in"
import IconLogOut from "~icons/lucide/log-out" import IconLogOut from "~icons/lucide/log-out"
import { activeActions$, invokeAction } from "~/helpers/actions" import { activeActions$, invokeAction } from "~/helpers/actions"
import { Container } from "dioc"
type Doc = { type Doc = {
text: string text: string
@@ -59,15 +60,18 @@ export class UserSpotlightSearcherService extends StaticSpotlightSearcherService
}, },
}) })
constructor() { // TODO: Constructors are no longer recommended as of dioc > 3, move to onServiceInit
super({ constructor(c: Container) {
super(c, {
searchFields: ["text", "alternates"], searchFields: ["text", "alternates"],
fieldWeights: { fieldWeights: {
text: 2, text: 2,
alternates: 1, alternates: 1,
}, },
}) })
}
override onServiceInit(): void {
this.setDocuments(this.documents) this.setDocuments(this.documents)
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -21,7 +21,7 @@ import {
StaticSpotlightSearcherService, StaticSpotlightSearcherService,
} from "./base/static.searcher" } from "./base/static.searcher"
import { Service } from "dioc" import { Container, Service } from "dioc"
import * as E from "fp-ts/Either" import * as E from "fp-ts/Either"
import MiniSearch from "minisearch" import MiniSearch from "minisearch"
import IconCheckCircle from "~/components/app/spotlight/entry/IconSelected.vue" import IconCheckCircle from "~/components/app/spotlight/entry/IconSelected.vue"
@@ -102,15 +102,18 @@ export class WorkspaceSpotlightSearcherService extends StaticSpotlightSearcherSe
}, },
}) })
constructor() { // TODO: Constructors are no longer recommended as of dioc > 3, move to onServiceInit
super({ constructor(c: Container) {
super(c, {
searchFields: ["text", "alternates"], searchFields: ["text", "alternates"],
fieldWeights: { fieldWeights: {
text: 2, text: 2,
alternates: 1, alternates: 1,
}, },
}) })
}
override onServiceInit() {
this.setDocuments(this.documents) this.setDocuments(this.documents)
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }
@@ -166,9 +169,7 @@ export class SwitchWorkspaceSpotlightSearcherService
private readonly spotlight = this.bind(SpotlightService) private readonly spotlight = this.bind(SpotlightService)
private readonly workspaceService = this.bind(WorkspaceService) private readonly workspaceService = this.bind(WorkspaceService)
constructor() { override onServiceInit() {
super()
this.spotlight.registerSearcher(this) this.spotlight.registerSearcher(this)
} }

View File

@@ -6,9 +6,7 @@ import { reactive } from "vue"
class MockTabService extends TabService<{ request: string }> { class MockTabService extends TabService<{ request: string }> {
public static readonly ID = "MOCK_TAB_SERVICE" public static readonly ID = "MOCK_TAB_SERVICE"
constructor() { override onServiceInit() {
super()
this.tabMap = reactive( this.tabMap = reactive(
new Map([ new Map([
[ [

View File

@@ -3,12 +3,15 @@ import { getDefaultGQLRequest } from "~/helpers/graphql/default"
import { HoppGQLDocument, HoppGQLSaveContext } from "~/helpers/graphql/document" import { HoppGQLDocument, HoppGQLSaveContext } from "~/helpers/graphql/document"
import { TabService } from "./tab" import { TabService } from "./tab"
import { computed } from "vue" import { computed } from "vue"
import { Container } from "dioc"
export class GQLTabService extends TabService<HoppGQLDocument> { export class GQLTabService extends TabService<HoppGQLDocument> {
public static readonly ID = "GQL_TAB_SERVICE" public static readonly ID = "GQL_TAB_SERVICE"
constructor() { // TODO: Moving this to `onServiceInit` breaks `persistableTabState`
super() // Figure out how to fix this
constructor(c: Container) {
super(c)
this.tabMap.set("test", { this.tabMap.set("test", {
id: "test", id: "test",

View File

@@ -3,12 +3,15 @@ import { computed } from "vue"
import { getDefaultRESTRequest } from "~/helpers/rest/default" import { getDefaultRESTRequest } from "~/helpers/rest/default"
import { HoppRESTDocument, HoppRESTSaveContext } from "~/helpers/rest/document" import { HoppRESTDocument, HoppRESTSaveContext } from "~/helpers/rest/document"
import { TabService } from "./tab" import { TabService } from "./tab"
import { Container } from "dioc"
export class RESTTabService extends TabService<HoppRESTDocument> { export class RESTTabService extends TabService<HoppRESTDocument> {
public static readonly ID = "REST_TAB_SERVICE" public static readonly ID = "REST_TAB_SERVICE"
constructor() { // TODO: Moving this to `onServiceInit` breaks `persistableTabState`
super() // Figure out how to fix this
constructor(c: Container) {
super(c)
this.tabMap.set("test", { this.tabMap.set("test", {
id: "test", id: "test",

View File

@@ -48,8 +48,7 @@ export class WorkspaceService extends Service<WorkspaceServiceEvent> {
-1 -1
) )
constructor() { override onServiceInit() {
super()
// Dispose the managed team list adapter when the user logs out // Dispose the managed team list adapter when the user logs out
// and initialize it when the user logs in // and initialize it when the user logs in
watch( watch(

View File

@@ -1,7 +1,7 @@
{ {
"name": "@hoppscotch/selfhost-desktop", "name": "@hoppscotch/selfhost-desktop",
"private": true, "private": true,
"version": "2024.3.0", "version": "2024.3.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev:vite": "vite", "dev:vite": "vite",
@@ -23,7 +23,7 @@
"@vueuse/core": "10.5.0", "@vueuse/core": "10.5.0",
"axios": "0.21.4", "axios": "0.21.4",
"buffer": "6.0.3", "buffer": "6.0.3",
"dioc": "1.0.1", "dioc": "3.0.1",
"environments.api": "link:@platform/environments/environments.api", "environments.api": "link:@platform/environments/environments.api",
"event": "link:@tauri-apps/api/event", "event": "link:@tauri-apps/api/event",
"fp-ts": "2.16.1", "fp-ts": "2.16.1",
@@ -78,4 +78,4 @@
"vite-plugin-vue-layouts": "0.7.0", "vite-plugin-vue-layouts": "0.7.0",
"vue-tsc": "1.8.8" "vue-tsc": "1.8.8"
} }
} }

View File

@@ -1260,7 +1260,7 @@ dependencies = [
[[package]] [[package]]
name = "hoppscotch-desktop" name = "hoppscotch-desktop"
version = "24.3.1" version = "24.3.2"
dependencies = [ dependencies = [
"cocoa 0.25.0", "cocoa 0.25.0",
"hex_color", "hex_color",

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "hoppscotch-desktop" name = "hoppscotch-desktop"
version = "24.3.0" version = "24.3.3"
description = "A Tauri App" description = "A Tauri App"
authors = ["you"] authors = ["you"]
license = "" license = ""

View File

@@ -8,7 +8,7 @@
}, },
"package": { "package": {
"productName": "Hoppscotch", "productName": "Hoppscotch",
"version": "24.3.1" "version": "24.3.3"
}, },
"tauri": { "tauri": {
"allowlist": { "allowlist": {

View File

@@ -138,10 +138,6 @@ export class NativeInterceptorService extends Service implements Interceptor {
public cookieJarService = this.bind(CookieJarService) public cookieJarService = this.bind(CookieJarService)
constructor() {
super()
}
public runRequest(req: any) { public runRequest(req: any) {
const processedReq = preProcessRequest(req) const processedReq = preProcessRequest(req)

View File

@@ -1,7 +1,7 @@
{ {
"name": "@hoppscotch/selfhost-web", "name": "@hoppscotch/selfhost-web",
"private": true, "private": true,
"version": "2024.3.1", "version": "2024.3.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev:vite": "vite", "dev:vite": "vite",

View File

@@ -52,7 +52,8 @@
"title": "Server is restarting" "title": "Server is restarting"
}, },
"save_changes": "Save Changes", "save_changes": "Save Changes",
"title": "Configurations" "title": "Configurations",
"update_failure": "Failed to update server configurations"
}, },
"data_sharing": { "data_sharing": {
"description": "Share anonymous data usage to improve Hoppscotch", "description": "Share anonymous data usage to improve Hoppscotch",

View File

@@ -1,7 +1,7 @@
{ {
"name": "hoppscotch-sh-admin", "name": "hoppscotch-sh-admin",
"private": true, "private": true,
"version": "2024.3.1", "version": "2024.3.3",
"type": "module", "type": "module",
"scripts": { "scripts": {
"dev": "pnpm exec npm-run-all -p -l dev:*", "dev": "pnpm exec npm-run-all -p -l dev:*",

View File

@@ -69,18 +69,18 @@
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { reactive } from 'vue'; import { reactive } from 'vue';
import { useI18n } from '~/composables/i18n'; import { useI18n } from '~/composables/i18n';
import { Config, SsoAuthProviders } from '~/composables/useConfigHandler'; import { ServerConfigs, SsoAuthProviders } from '~/helpers/configs';
import IconEye from '~icons/lucide/eye'; import IconEye from '~icons/lucide/eye';
import IconEyeOff from '~icons/lucide/eye-off'; import IconEyeOff from '~icons/lucide/eye-off';
const t = useI18n(); const t = useI18n();
const props = defineProps<{ const props = defineProps<{
config: Config; config: ServerConfigs;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:config', v: Config): void; (e: 'update:config', v: ServerConfigs): void;
}>(); }>();
const workingConfigs = useVModel(props, 'config', emit); const workingConfigs = useVModel(props, 'config', emit);
@@ -93,7 +93,7 @@ const capitalize = (text: string) =>
type ProviderFieldKeys = keyof ProviderFields; type ProviderFieldKeys = keyof ProviderFields;
type ProviderFields = { type ProviderFields = {
[Field in keyof Config['providers'][SsoAuthProviders]['fields']]: boolean; [Field in keyof ServerConfigs['providers'][SsoAuthProviders]['fields']]: boolean;
} & Partial<{ tenant: boolean }>; } & Partial<{ tenant: boolean }>;
type ProviderFieldMetadata = { type ProviderFieldMetadata = {

View File

@@ -9,14 +9,14 @@
<script setup lang="ts"> <script setup lang="ts">
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { Config } from '~/composables/useConfigHandler'; import { ServerConfigs } from '~/helpers/configs';
const props = defineProps<{ const props = defineProps<{
config: Config; config: ServerConfigs;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:config', v: Config): void; (e: 'update:config', v: ServerConfigs): void;
}>(); }>();
const workingConfigs = useVModel(props, 'config', emit); const workingConfigs = useVModel(props, 'config', emit);

View File

@@ -38,17 +38,17 @@
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { computed } from 'vue'; import { computed } from 'vue';
import { useI18n } from '~/composables/i18n'; import { useI18n } from '~/composables/i18n';
import { Config } from '~/composables/useConfigHandler'; import { ServerConfigs } from '~/helpers/configs';
import IconShieldQuestion from '~icons/lucide/shield-question'; import IconShieldQuestion from '~icons/lucide/shield-question';
const t = useI18n(); const t = useI18n();
const props = defineProps<{ const props = defineProps<{
config: Config; config: ServerConfigs;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:config', v: Config): void; (e: 'update:config', v: ServerConfigs): void;
}>(); }>();
const workingConfigs = useVModel(props, 'config', emit); const workingConfigs = useVModel(props, 'config', emit);

View File

@@ -17,20 +17,21 @@ import { useMutation } from '@urql/vue';
import { onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useI18n } from '~/composables/i18n'; import { useI18n } from '~/composables/i18n';
import { useToast } from '~/composables/toast'; import { useToast } from '~/composables/toast';
import { Config, useConfigHandler } from '~/composables/useConfigHandler'; import { useConfigHandler } from '~/composables/useConfigHandler';
import { import {
EnableAndDisableSsoDocument, EnableAndDisableSsoDocument,
ResetInfraConfigsDocument, ResetInfraConfigsDocument,
UpdateInfraConfigsDocument,
ToggleAnalyticsCollectionDocument, ToggleAnalyticsCollectionDocument,
UpdateInfraConfigsDocument,
} from '~/helpers/backend/graphql'; } from '~/helpers/backend/graphql';
import { ServerConfigs } from '~/helpers/configs';
const t = useI18n(); const t = useI18n();
const toast = useToast(); const toast = useToast();
const props = withDefaults( const props = withDefaults(
defineProps<{ defineProps<{
workingConfigs?: Config; workingConfigs?: ServerConfigs;
reset?: boolean; reset?: boolean;
}>(), }>(),
{ {

View File

@@ -58,18 +58,18 @@
import { useVModel } from '@vueuse/core'; import { useVModel } from '@vueuse/core';
import { computed, reactive } from 'vue'; import { computed, reactive } from 'vue';
import { useI18n } from '~/composables/i18n'; import { useI18n } from '~/composables/i18n';
import { Config } from '~/composables/useConfigHandler'; import { ServerConfigs } from '~/helpers/configs';
import IconEye from '~icons/lucide/eye'; import IconEye from '~icons/lucide/eye';
import IconEyeOff from '~icons/lucide/eye-off'; import IconEyeOff from '~icons/lucide/eye-off';
const t = useI18n(); const t = useI18n();
const props = defineProps<{ const props = defineProps<{
config: Config; config: ServerConfigs;
}>(); }>();
const emit = defineEmits<{ const emit = defineEmits<{
(e: 'update:config', v: Config): void; (e: 'update:config', v: ServerConfigs): void;
}>(); }>();
const workingConfigs = useVModel(props, 'config', emit); const workingConfigs = useVModel(props, 'config', emit);
@@ -87,7 +87,7 @@ const smtpConfigs = computed({
// Mask sensitive fields // Mask sensitive fields
type Field = { type Field = {
name: string; name: string;
key: keyof Config['mailConfigs']['fields']; key: keyof ServerConfigs['mailConfigs']['fields'];
}; };
const smtpConfigFields = reactive<Field[]>([ const smtpConfigFields = reactive<Field[]>([
@@ -100,10 +100,10 @@ const maskState = reactive<Record<string, boolean>>({
mailer_from_address: true, mailer_from_address: true,
}); });
const toggleMask = (fieldKey: keyof Config['mailConfigs']['fields']) => { const toggleMask = (fieldKey: keyof ServerConfigs['mailConfigs']['fields']) => {
maskState[fieldKey] = !maskState[fieldKey]; maskState[fieldKey] = !maskState[fieldKey];
}; };
const isMasked = (fieldKey: keyof Config['mailConfigs']['fields']) => const isMasked = (fieldKey: keyof ServerConfigs['mailConfigs']['fields']) =>
maskState[fieldKey]; maskState[fieldKey];
</script> </script>

View File

@@ -1,83 +1,39 @@
import { AnyVariables, UseMutationResponse } from '@urql/vue'; import { AnyVariables, UseMutationResponse } from '@urql/vue';
import { cloneDeep } from 'lodash-es'; import { cloneDeep } from 'lodash-es';
import { computed, onMounted, ref } from 'vue'; import { onMounted, ref } from 'vue';
import { useI18n } from '~/composables/i18n'; import { useI18n } from '~/composables/i18n';
import { import {
AllowedAuthProvidersDocument, AllowedAuthProvidersDocument,
AuthProvider,
EnableAndDisableSsoArgs, EnableAndDisableSsoArgs,
EnableAndDisableSsoMutation, EnableAndDisableSsoMutation,
InfraConfigArgs, InfraConfigArgs,
InfraConfigEnum, InfraConfigEnum,
InfraConfigsDocument, InfraConfigsDocument,
ResetInfraConfigsMutation, ResetInfraConfigsMutation,
ServiceStatus,
ToggleAnalyticsCollectionMutation, ToggleAnalyticsCollectionMutation,
UpdateInfraConfigsMutation, UpdateInfraConfigsMutation,
} from '~/helpers/backend/graphql'; } from '~/helpers/backend/graphql';
import {
ALL_CONFIGS,
ConfigSection,
ConfigTransform,
GITHUB_CONFIGS,
GOOGLE_CONFIGS,
MAIL_CONFIGS,
MICROSOFT_CONFIGS,
ServerConfigs,
UpdatedConfigs,
} from '~/helpers/configs';
import { useToast } from './toast'; import { useToast } from './toast';
import { useClientHandler } from './useClientHandler'; import { useClientHandler } from './useClientHandler';
// Types
export type SsoAuthProviders = 'google' | 'microsoft' | 'github';
export type Config = {
providers: {
google: {
name: SsoAuthProviders;
enabled: boolean;
fields: {
client_id: string;
client_secret: string;
callback_url: string;
scope: string;
};
};
github: {
name: SsoAuthProviders;
enabled: boolean;
fields: {
client_id: string;
client_secret: string;
callback_url: string;
scope: string;
};
};
microsoft: {
name: SsoAuthProviders;
enabled: boolean;
fields: {
client_id: string;
client_secret: string;
callback_url: string;
scope: string;
tenant: string;
};
};
};
mailConfigs: {
name: string;
enabled: boolean;
fields: {
mailer_smtp_url: string;
mailer_from_address: string;
};
};
dataSharingConfigs: {
name: string;
enabled: boolean;
};
};
type UpdatedConfigs = {
name: string;
value: string;
};
/** Composable that handles all operations related to server configurations /** Composable that handles all operations related to server configurations
* @param updatedConfigs A Config Object contatining the updated configs * @param updatedConfigs A Config Object contatining the updated configs
*/ */
export function useConfigHandler(updatedConfigs?: Config) { export function useConfigHandler(updatedConfigs?: ServerConfigs) {
const t = useI18n(); const t = useI18n();
const toast = useToast(); const toast = useToast();
@@ -90,24 +46,9 @@ export function useConfigHandler(updatedConfigs?: Config) {
} = useClientHandler( } = useClientHandler(
InfraConfigsDocument, InfraConfigsDocument,
{ {
configNames: [ configNames: ALL_CONFIGS.flat().map(
'GOOGLE_CLIENT_ID', ({ name }) => name
'GOOGLE_CLIENT_SECRET', ) as InfraConfigEnum[],
'GOOGLE_CALLBACK_URL',
'GOOGLE_SCOPE',
'MICROSOFT_CLIENT_ID',
'MICROSOFT_CLIENT_SECRET',
'MICROSOFT_CALLBACK_URL',
'MICROSOFT_SCOPE',
'MICROSOFT_TENANT',
'GITHUB_CLIENT_ID',
'GITHUB_CLIENT_SECRET',
'GITHUB_CALLBACK_URL',
'GITHUB_SCOPE',
'MAILER_SMTP_URL',
'MAILER_ADDRESS_FROM',
'ALLOW_ANALYTICS_COLLECTION',
] as InfraConfigEnum[],
}, },
(x) => x.infraConfigs (x) => x.infraConfigs
); );
@@ -125,14 +66,14 @@ export function useConfigHandler(updatedConfigs?: Config) {
); );
// Current and working configs // Current and working configs
const currentConfigs = ref<Config>(); const currentConfigs = ref<ServerConfigs>();
const workingConfigs = ref<Config>(); const workingConfigs = ref<ServerConfigs>();
onMounted(async () => { onMounted(async () => {
await fetchInfraConfigs(); await fetchInfraConfigs();
await fetchAllowedAuthProviders(); await fetchAllowedAuthProviders();
const getFieldValue = (name: string) => const getFieldValue = (name: InfraConfigEnum) =>
infraConfigs.value.find((x) => x.name === name)?.value ?? ''; infraConfigs.value.find((x) => x.name === name)?.value ?? '';
// Transforming the fetched data into a Configs object // Transforming the fetched data into a Configs object
@@ -140,42 +81,42 @@ export function useConfigHandler(updatedConfigs?: Config) {
providers: { providers: {
google: { google: {
name: 'google', name: 'google',
enabled: allowedAuthProviders.value.includes('GOOGLE'), enabled: allowedAuthProviders.value.includes(AuthProvider.Google),
fields: { fields: {
client_id: getFieldValue('GOOGLE_CLIENT_ID'), client_id: getFieldValue(InfraConfigEnum.GoogleClientId),
client_secret: getFieldValue('GOOGLE_CLIENT_SECRET'), client_secret: getFieldValue(InfraConfigEnum.GoogleClientSecret),
callback_url: getFieldValue('GOOGLE_CALLBACK_URL'), callback_url: getFieldValue(InfraConfigEnum.GoogleCallbackUrl),
scope: getFieldValue('GOOGLE_SCOPE'), scope: getFieldValue(InfraConfigEnum.GoogleScope),
}, },
}, },
github: { github: {
name: 'github', name: 'github',
enabled: allowedAuthProviders.value.includes('GITHUB'), enabled: allowedAuthProviders.value.includes(AuthProvider.Github),
fields: { fields: {
client_id: getFieldValue('GITHUB_CLIENT_ID'), client_id: getFieldValue(InfraConfigEnum.GithubClientId),
client_secret: getFieldValue('GITHUB_CLIENT_SECRET'), client_secret: getFieldValue(InfraConfigEnum.GithubClientSecret),
callback_url: getFieldValue('GITHUB_CALLBACK_URL'), callback_url: getFieldValue(InfraConfigEnum.GoogleCallbackUrl),
scope: getFieldValue('GITHUB_SCOPE'), scope: getFieldValue(InfraConfigEnum.GithubScope),
}, },
}, },
microsoft: { microsoft: {
name: 'microsoft', name: 'microsoft',
enabled: allowedAuthProviders.value.includes('MICROSOFT'), enabled: allowedAuthProviders.value.includes(AuthProvider.Microsoft),
fields: { fields: {
client_id: getFieldValue('MICROSOFT_CLIENT_ID'), client_id: getFieldValue(InfraConfigEnum.MicrosoftClientId),
client_secret: getFieldValue('MICROSOFT_CLIENT_SECRET'), client_secret: getFieldValue(InfraConfigEnum.MicrosoftClientSecret),
callback_url: getFieldValue('MICROSOFT_CALLBACK_URL'), callback_url: getFieldValue(InfraConfigEnum.MicrosoftCallbackUrl),
scope: getFieldValue('MICROSOFT_SCOPE'), scope: getFieldValue(InfraConfigEnum.MicrosoftScope),
tenant: getFieldValue('MICROSOFT_TENANT'), tenant: getFieldValue(InfraConfigEnum.MicrosoftTenant),
}, },
}, },
}, },
mailConfigs: { mailConfigs: {
name: 'email', name: 'email',
enabled: allowedAuthProviders.value.includes('EMAIL'), enabled: allowedAuthProviders.value.includes(AuthProvider.Email),
fields: { fields: {
mailer_smtp_url: getFieldValue('MAILER_SMTP_URL'), mailer_smtp_url: getFieldValue(InfraConfigEnum.MailerSmtpUrl),
mailer_from_address: getFieldValue('MAILER_ADDRESS_FROM'), mailer_from_address: getFieldValue(InfraConfigEnum.MailerAddressFrom),
}, },
}, },
dataSharingConfigs: { dataSharingConfigs: {
@@ -191,138 +132,13 @@ export function useConfigHandler(updatedConfigs?: Config) {
workingConfigs.value = cloneDeep(currentConfigs.value); workingConfigs.value = cloneDeep(currentConfigs.value);
}); });
// Transforming the working configs back into the format required by the mutations /*
const updatedInfraConfigs = computed(() => { Check if any of the config fields are empty
let config: UpdatedConfigs[] = [ */
{
name: '',
value: '',
},
];
if (updatedConfigs?.providers.google.enabled) {
config.push(
{
name: 'GOOGLE_CLIENT_ID',
value: updatedConfigs?.providers.google.fields.client_id ?? '',
},
{
name: 'GOOGLE_CLIENT_SECRET',
value: updatedConfigs?.providers.google.fields.client_secret ?? '',
},
{
name: 'GOOGLE_CALLBACK_URL',
value: updatedConfigs?.providers.google.fields.callback_url ?? '',
},
{
name: 'GOOGLE_SCOPE',
value: updatedConfigs?.providers.google.fields.scope ?? '',
}
);
} else {
config = config.filter(
(item) =>
item.name !== 'GOOGLE_CLIENT_ID' &&
item.name !== 'GOOGLE_CLIENT_SECRET' &&
item.name !== 'GOOGLE_CALLBACK_URL' &&
item.name !== 'GOOGLE_SCOPE'
);
}
if (updatedConfigs?.providers.microsoft.enabled) {
config.push(
{
name: 'MICROSOFT_CLIENT_ID',
value: updatedConfigs?.providers.microsoft.fields.client_id ?? '',
},
{
name: 'MICROSOFT_CLIENT_SECRET',
value: updatedConfigs?.providers.microsoft.fields.client_secret ?? '',
},
{
name: 'MICROSOFT_CALLBACK_URL',
value: updatedConfigs?.providers.microsoft.fields.callback_url ?? '',
},
{
name: 'MICROSOFT_SCOPE',
value: updatedConfigs?.providers.microsoft.fields.scope ?? '',
},
{
name: 'MICROSOFT_TENANT',
value: updatedConfigs?.providers.microsoft.fields.tenant ?? '',
}
);
} else {
config = config.filter(
(item) =>
item.name !== 'MICROSOFT_CLIENT_ID' &&
item.name !== 'MICROSOFT_CLIENT_SECRET' &&
item.name !== 'MICROSOFT_CALLBACK_URL' &&
item.name !== 'MICROSOFT_SCOPE' &&
item.name !== 'MICROSOFT_TENANT'
);
}
if (updatedConfigs?.providers.github.enabled) {
config.push(
{
name: 'GITHUB_CLIENT_ID',
value: updatedConfigs?.providers.github.fields.client_id ?? '',
},
{
name: 'GITHUB_CLIENT_SECRET',
value: updatedConfigs?.providers.github.fields.client_secret ?? '',
},
{
name: 'GITHUB_CALLBACK_URL',
value: updatedConfigs?.providers.github.fields.callback_url ?? '',
},
{
name: 'GITHUB_SCOPE',
value: updatedConfigs?.providers.github.fields.scope ?? '',
}
);
} else {
config = config.filter(
(item) =>
item.name !== 'GITHUB_CLIENT_ID' &&
item.name !== 'GITHUB_CLIENT_SECRET' &&
item.name !== 'GITHUB_CALLBACK_URL' &&
item.name !== 'GITHUB_SCOPE'
);
}
if (updatedConfigs?.mailConfigs.enabled) {
config.push(
{
name: 'MAILER_SMTP_URL',
value: updatedConfigs?.mailConfigs.fields.mailer_smtp_url ?? '',
},
{
name: 'MAILER_ADDRESS_FROM',
value: updatedConfigs?.mailConfigs.fields.mailer_from_address ?? '',
}
);
} else {
config = config.filter(
(item) =>
item.name !== 'MAILER_SMTP_URL' && item.name !== 'MAILER_ADDRESS_FROM'
);
}
config = config.filter((item) => item.name !== '');
return config;
});
// Checking if any of the config fields are empty
const isFieldEmpty = (field: string) => field.trim() === ''; const isFieldEmpty = (field: string) => field.trim() === '';
type ConfigSection = { const AreAnyConfigFieldsEmpty = (config: ServerConfigs): boolean => {
enabled: boolean;
fields: Record<string, string>;
};
const AreAnyConfigFieldsEmpty = (config: Config): boolean => {
const sections: Array<ConfigSection> = [ const sections: Array<ConfigSection> = [
config.providers.github, config.providers.github,
config.providers.google, config.providers.google,
@@ -337,28 +153,44 @@ export function useConfigHandler(updatedConfigs?: Config) {
}; };
// Transforming the working configs back into the format required by the mutations // Transforming the working configs back into the format required by the mutations
const updatedAllowedAuthProviders = computed(() => { const transformInfraConfigs = () => {
return [ const updatedWorkingConfigs: ConfigTransform[] = [
{ {
provider: 'GOOGLE', config: GOOGLE_CONFIGS,
status: updatedConfigs?.providers.google.enabled ? 'ENABLE' : 'DISABLE', enabled: updatedConfigs?.providers.google.enabled,
fields: updatedConfigs?.providers.google.fields,
}, },
{ {
provider: 'MICROSOFT', config: GITHUB_CONFIGS,
status: updatedConfigs?.providers.microsoft.enabled enabled: updatedConfigs?.providers.github.enabled,
? 'ENABLE' fields: updatedConfigs?.providers.github.fields,
: 'DISABLE',
}, },
{ {
provider: 'GITHUB', config: MICROSOFT_CONFIGS,
status: updatedConfigs?.providers.github.enabled ? 'ENABLE' : 'DISABLE', enabled: updatedConfigs?.providers.microsoft.enabled,
fields: updatedConfigs?.providers.microsoft.fields,
}, },
{ {
provider: 'EMAIL', config: MAIL_CONFIGS,
status: updatedConfigs?.mailConfigs.enabled ? 'ENABLE' : 'DISABLE', enabled: updatedConfigs?.mailConfigs.enabled,
fields: updatedConfigs?.mailConfigs.fields,
}, },
]; ];
});
const transformedConfigs: UpdatedConfigs[] = [];
updatedWorkingConfigs.forEach(({ config, enabled, fields }) => {
config.forEach(({ name, key }) => {
if (enabled && fields) {
const value =
typeof fields === 'string' ? fields : String(fields[key]);
transformedConfigs.push({ name, value });
}
});
});
return transformedConfigs;
};
// Generic function to handle mutation execution and error handling // Generic function to handle mutation execution and error handling
const executeMutation = async <T, V>( const executeMutation = async <T, V>(
@@ -379,27 +211,59 @@ export function useConfigHandler(updatedConfigs?: Config) {
// Updating the auth provider configurations // Updating the auth provider configurations
const updateAuthProvider = ( const updateAuthProvider = (
updateProviderStatus: UseMutationResponse<EnableAndDisableSsoMutation> updateProviderStatus: UseMutationResponse<EnableAndDisableSsoMutation>
) => ) => {
executeMutation( const updatedAllowedAuthProviders: EnableAndDisableSsoArgs[] = [
{
provider: AuthProvider.Google,
status: updatedConfigs?.providers.google.enabled
? ServiceStatus.Enable
: ServiceStatus.Disable,
},
{
provider: AuthProvider.Microsoft,
status: updatedConfigs?.providers.microsoft.enabled
? ServiceStatus.Enable
: ServiceStatus.Disable,
},
{
provider: AuthProvider.Github,
status: updatedConfigs?.providers.github.enabled
? ServiceStatus.Enable
: ServiceStatus.Disable,
},
{
provider: AuthProvider.Email,
status: updatedConfigs?.mailConfigs.enabled
? ServiceStatus.Enable
: ServiceStatus.Disable,
},
];
return executeMutation(
updateProviderStatus, updateProviderStatus,
{ {
providerInfo: providerInfo: updatedAllowedAuthProviders,
updatedAllowedAuthProviders.value as EnableAndDisableSsoArgs[],
}, },
'configs.auth_providers.update_failure' 'configs.auth_providers.update_failure'
); );
};
// Updating the infra configurations // Updating the infra configurations
const updateInfraConfigs = ( const updateInfraConfigs = (
updateInfraConfigsMutation: UseMutationResponse<UpdateInfraConfigsMutation> updateInfraConfigsMutation: UseMutationResponse<UpdateInfraConfigsMutation>
) => ) => {
executeMutation( const infraConfigs: InfraConfigArgs[] = updatedConfigs
? transformInfraConfigs()
: [];
return executeMutation(
updateInfraConfigsMutation, updateInfraConfigsMutation,
{ {
infraConfigs: updatedInfraConfigs.value as InfraConfigArgs[], infraConfigs,
}, },
'configs.mail_configs.update_failure' 'configs.update_failure'
); );
};
// Resetting the infra configurations // Resetting the infra configurations
const resetInfraConfigs = ( const resetInfraConfigs = (
@@ -411,7 +275,6 @@ export function useConfigHandler(updatedConfigs?: Config) {
'configs.reset.failure' 'configs.reset.failure'
); );
// Updating the data sharing configurations
const updateDataSharingConfigs = ( const updateDataSharingConfigs = (
toggleDataSharingMutation: UseMutationResponse<ToggleAnalyticsCollectionMutation> toggleDataSharingMutation: UseMutationResponse<ToggleAnalyticsCollectionMutation>
) => ) =>
@@ -419,8 +282,8 @@ export function useConfigHandler(updatedConfigs?: Config) {
toggleDataSharingMutation, toggleDataSharingMutation,
{ {
status: updatedConfigs?.dataSharingConfigs.enabled status: updatedConfigs?.dataSharingConfigs.enabled
? 'ENABLE' ? ServiceStatus.Enable
: 'DISABLE', : ServiceStatus.Disable,
}, },
'configs.data_sharing.update_failure' 'configs.data_sharing.update_failure'
); );
@@ -428,8 +291,6 @@ export function useConfigHandler(updatedConfigs?: Config) {
return { return {
currentConfigs, currentConfigs,
workingConfigs, workingConfigs,
updatedInfraConfigs,
updatedAllowedAuthProviders,
updateAuthProvider, updateAuthProvider,
updateDataSharingConfigs, updateDataSharingConfigs,
updateInfraConfigs, updateInfraConfigs,

View File

@@ -0,0 +1,160 @@
import { InfraConfigEnum } from './backend/graphql';
export type SsoAuthProviders = 'google' | 'microsoft' | 'github';
export type ServerConfigs = {
providers: {
google: {
name: SsoAuthProviders;
enabled: boolean;
fields: {
client_id: string;
client_secret: string;
callback_url: string;
scope: string;
};
};
github: {
name: SsoAuthProviders;
enabled: boolean;
fields: {
client_id: string;
client_secret: string;
callback_url: string;
scope: string;
};
};
microsoft: {
name: SsoAuthProviders;
enabled: boolean;
fields: {
client_id: string;
client_secret: string;
callback_url: string;
scope: string;
tenant: string;
};
};
};
mailConfigs: {
name: string;
enabled: boolean;
fields: {
mailer_smtp_url: string;
mailer_from_address: string;
};
};
dataSharingConfigs: {
name: string;
enabled: boolean;
};
};
export type UpdatedConfigs = {
name: InfraConfigEnum;
value: string;
};
export type ConfigTransform = {
config: Config[];
enabled?: boolean;
fields?: Record<string, string | boolean> | string;
};
export type ConfigSection = {
enabled: boolean;
fields: Record<string, string>;
};
export type Config = {
name: InfraConfigEnum;
key: string;
};
export const GOOGLE_CONFIGS: Config[] = [
{
name: InfraConfigEnum.GoogleClientId,
key: 'client_id',
},
{
name: InfraConfigEnum.GoogleClientSecret,
key: 'client_secret',
},
{
name: InfraConfigEnum.GoogleCallbackUrl,
key: 'callback_url',
},
{
name: InfraConfigEnum.GoogleScope,
key: 'scope',
},
];
export const MICROSOFT_CONFIGS: Config[] = [
{
name: InfraConfigEnum.MicrosoftClientId,
key: 'client_id',
},
{
name: InfraConfigEnum.MicrosoftClientSecret,
key: 'client_secret',
},
{
name: InfraConfigEnum.MicrosoftCallbackUrl,
key: 'callback_url',
},
{
name: InfraConfigEnum.MicrosoftScope,
key: 'scope',
},
{
name: InfraConfigEnum.MicrosoftTenant,
key: 'tenant',
},
];
export const GITHUB_CONFIGS: Config[] = [
{
name: InfraConfigEnum.GithubClientId,
key: 'client_id',
},
{
name: InfraConfigEnum.GithubClientSecret,
key: 'client_secret',
},
{
name: InfraConfigEnum.GithubCallbackUrl,
key: 'callback_url',
},
{
name: InfraConfigEnum.GithubScope,
key: 'scope',
},
];
export const MAIL_CONFIGS: Config[] = [
{
name: InfraConfigEnum.MailerSmtpUrl,
key: 'mailer_smtp_url',
},
{
name: InfraConfigEnum.MailerAddressFrom,
key: 'mailer_from_address',
},
];
const DATA_SHARING_CONFIGS: Omit<Config, 'key'>[] = [
{
name: InfraConfigEnum.AllowAnalyticsCollection,
},
];
export const ALL_CONFIGS = [
GOOGLE_CONFIGS,
MICROSOFT_CONFIGS,
GITHUB_CONFIGS,
MAIL_CONFIGS,
DATA_SHARING_CONFIGS,
];

View File

@@ -208,13 +208,14 @@ const deleteUserMutation = async (id: string | null) => {
if (result.error) { if (result.error) {
toast.error(t('state.delete_user_failure')); toast.error(t('state.delete_user_failure'));
} else { } else {
const deletedUsers = result.data?.removeUsersByAdmin || []; const deletedUser = result.data?.removeUsersByAdmin || [];
handleUserDeletion(deletedUser);
handleUserDeletion(deletedUsers); const { isDeleted } = deletedUser[0];
if (isDeleted) router.push('/users');
} }
confirmDeletion.value = false; confirmDeletion.value = false;
deleteUserUID.value = null; deleteUserUID.value = null;
!result.error && router.push('/users');
}; };
</script> </script>

34120
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff