Skip to content

Commit

Permalink
forms-1782 Call Tenant API Post Successful CHEFS Login
Browse files Browse the repository at this point in the history
  • Loading branch information
bhuvan-aot committed Feb 27, 2025
1 parent e4dbf43 commit 00881bb
Show file tree
Hide file tree
Showing 16 changed files with 320 additions and 3 deletions.
3 changes: 3 additions & 0 deletions .devcontainer/chefs_local/local.sample.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@
"tokenEndpoint": "https://dev.loginproxy.gov.bc.ca/auth/realms/comsvcauth/protocol/openid-connect/token",
"clientId": "CDOGS_CLIENT_ID",
"clientSecret": "CDOGS_CLIENT_SECRET"
},
"tenantManagementService": {
"endpoint": "http://localhost:4144/v1"
}
}
},
Expand Down
3 changes: 3 additions & 0 deletions .devcontainer/chefs_local/test.json
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,9 @@
"tokenEndpoint": "https://dev.loginproxy.gov.bc.ca/auth/realms/comsvcauth/protocol/openid-connect/token",
"clientId": "CDOGS_CLIENT_ID",
"clientSecret": "CDOGS_CLIENT_SECRET"
},
"tenantManagementService": {
"endpoint": "http://localhost:4144/v1"
}
}
},
Expand Down
3 changes: 3 additions & 0 deletions app/config/custom-environment-variables.json
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,9 @@
"tokenEndpoint": "SC_CS_CDOGS_TOKEN_ENDPOINT",
"clientId": "SC_CS_CDOGS_CLIENT_ID",
"clientSecret": "SC_CS_CDOGS_CLIENT_SECRET"
},
"tenantManagementService": {
"endpoint": "SC_CS_TMS_ENDPOINT"
}
}
}
Expand Down
3 changes: 3 additions & 0 deletions app/config/default.json
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,9 @@
"tokenEndpoint": "https://dev.loginproxy.gov.bc.ca/auth/realms/comsvcauth/protocol/openid-connect/token",
"clientId": "CDOGS_CLIENT_ID",
"clientSecret": "CDOGS_CLIENT_SECRET"
},
"tenantManagementService": {
"endpoint": "http://localhost:4144/v1"
}
}
},
Expand Down
18 changes: 16 additions & 2 deletions app/frontend/src/router.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { createRouter, createWebHistory } from 'vue-router';

import { useAuthStore } from '~/store/auth';
import { useFormStore } from '~/store/form';
import { useTenantStore } from '~/store/tenant';
import { useIdpStore } from '~/store/identityProviders';
import { preFlightAuth } from '~/utils/permissionUtils';

Expand Down Expand Up @@ -404,6 +405,14 @@ export default function getRouter(basePath = '/') {
}
},
},
{
path: '/tenancy',
name: 'Tenancy',
component: () => import('~/views/Tenancy.vue'),
meta: {
requiresAuth: 'primary',
},
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
Expand All @@ -423,14 +432,14 @@ export default function getRouter(basePath = '/') {

const authStore = useAuthStore();
const idpStore = useIdpStore();
const tenantStore = useTenantStore();

if (isFirstTransition) {
// Always call rbac/current if authenticated and on first page load
if (authStore?.ready && authStore?.authenticated) {
const formStore = useFormStore();
formStore.getFormsForCurrentUser();
}

// Handle proper redirections on first page load
if (to.query.r) {
router.replace({
Expand All @@ -439,7 +448,12 @@ export default function getRouter(basePath = '/') {
});
}
}

if (authStore?.authenticated && !tenantStore.hasTenants) {
tenantStore.getTenantsForUser();
}
if (!tenantStore.selectedTenant && to.path !== '/tenancy') {
return next('/tenancy');
}
// Force login redirect if not authenticated
// Note some pages (Submit and Success) only require auth if the form being loaded is secured
// in those cases, see the beforeEnter navigation guards for auth loop determination
Expand Down
1 change: 1 addition & 0 deletions app/frontend/src/services/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,4 @@ export { default as fileService } from './fileService';
export { default as utilsService } from './utilsService';
export { default as encryptionKeyService } from './encryptionKeyService';
export { default as eventStreamConfigService } from './eventStreamConfigService';
export { default as tenantService } from './tenantService';
13 changes: 13 additions & 0 deletions app/frontend/src/services/tenantService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { appAxios } from '~/services/interceptors';
import { ApiRoutes } from '~/utils/constants';

export default {
/**
* @function getTenantsForUser
* Get the list of tenants current user belongs
* @returns {Promise} An axios response
*/
getTenantsForUser(params = {}) {
return appAxios().get(`${ApiRoutes.TENANTS}/me`, { params });
},
};
31 changes: 31 additions & 0 deletions app/frontend/src/store/tenant.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import { defineStore } from 'pinia';
import { tenantService } from '~/services';

export const useTenantStore = defineStore('tenant', {
state: () => ({
tenants: [],
selectedTenant: null,
}),
getters: {
hasTenants: (state) => state.tenants.length > 0,
},
actions: {
//
// Current User
//
//
async getTenantsForUser() {
try {
// Get the forms based on the user's permissions
const response = await tenantService.getTenantsForUser();
const data = response.data;
this.tenants = data;
} catch (error) {
throw new Error('something went wrong while getting tenants ', error);
}
},
setSelectedTenant(tenant) {
this.selectedTenant = tenant;
},
},
});
1 change: 1 addition & 0 deletions app/frontend/src/utils/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ export const ApiRoutes = Object.freeze({
FORM_METADATA: '/formMetadata',
EVENT_STREAM_CONFIG: '/eventStreamConfig',
ENCRYPTION_KEY: '/encryptionKey',
TENANTS: '/tenant',
});

/** Roles a user can have on a form. These are defined in the DB and sent from the API */
Expand Down
144 changes: 144 additions & 0 deletions app/frontend/src/views/Tenancy.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
<template>
<div class="tenancy-container">
<div class="alert-box">
<span class="close-btn" @click="dismissAlert">&times;</span>
<h2>
<strong>New Feature Alert!</strong> CHEFS now supports Multi-Tenancy
</h2>
<p>
You can now manage forms across multiple teams (tenancies) while keeping
data, settings, and permissions separate.
</p>
<ul>
<li>
🔹 Switch Between Tenancies – Select the tenancy you want to work in
from your available list.
</li>
<li>
🔹 Access the Older Version – Choose "Non-tenanted CHEFS" to return to
the previous setup.
</li>
</ul>
<a href="#">Learn more about Multi-Tenancy</a>
</div>

<div class="selection-container">
<label for="tenantSelect">Choose a Tenancy</label>
<select id="tenantSelect" v-model="selectedTenant">
<option disabled value="">Select an option...</option>
<option
v-for="tenant in tenantStore.tenants"
:key="tenant.id"
:value="tenant"
>
{{ tenant.name }}
</option>
</select>
<button
:disabled="!selectedTenant"
class="confirm-btn"
@click="confirmTenantSelection"
>
Go to Tenanted CHEFS →
</button>
<a href="#" class="old-chefs-link">Non-tenanted CHEFS ("Old" CHEFS)</a>
</div>
</div>
</template>

<script>
import { useTenantStore } from '~/store/tenant';
import { useRouter } from 'vue-router';
import { ref, onMounted } from 'vue';
export default {
setup() {
const tenantStore = useTenantStore();
const router = useRouter();
const selectedTenant = ref(null);
onMounted(async () => {
if (!tenantStore.hasTenants) {
await tenantStore.getTenantsForUser();
}
});
const confirmTenantSelection = () => {
if (selectedTenant.value) {
tenantStore.setSelectedTenant(selectedTenant.value);
router.push('/'); // Redirect to the About page
}
};
const dismissAlert = () => {
document.querySelector('.alert-box').style.display = 'none';
};
return {
tenantStore,
selectedTenant,
confirmTenantSelection,
dismissAlert,
};
},
};
</script>

<style scoped>
.tenancy-container {
max-width: 700px;
margin: auto;
text-align: center;
padding: 20px;
}
.alert-box {
background: #eef5ff;
padding: 15px;
border-left: 5px solid #0053a0;
margin-bottom: 20px;
position: relative;
text-align: left;
}
.alert-box h2 {
color: #003366;
}
.close-btn {
position: absolute;
top: 10px;
right: 15px;
font-size: 20px;
cursor: pointer;
}
.selection-container {
margin-top: 20px;
}
select {
width: 100%;
padding: 10px;
margin-bottom: 15px;
}
.confirm-btn {
background: #0053a0;
color: white;
padding: 10px 15px;
border: none;
cursor: pointer;
}
.confirm-btn:disabled {
background: #ccc;
cursor: not-allowed;
}
.old-chefs-link {
display: block;
margin-top: 10px;
text-decoration: underline;
}
</style>
54 changes: 54 additions & 0 deletions app/src/components/tenantManagementService.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const config = require('config');
const errorToProblem = require('./errorToProblem');
const SERVICE = 'tenantManagement';

class tenantManagementService {
constructor(apiUrl) {
if (!apiUrl) {
throw new Error('tenantManagementService is not configured. Check configuration.');
}
this.apiUrl = apiUrl;
}

async getTenantsByUserId() {
try {
const data = {
data: {
tenants: [
{
id: '31632dff-96f1-478e-bd77-24ebf2c93094',
name: 'Tenant 1',
ministryName: 'Ministry of Something',
createdDateTime: '2025-02-13T04:08:16.105Z',
updatedDateTime: '2025-02-13T04:08:16.105Z',
},
{
id: 'd46a6548-ba5e-47d8-b1b6-de2a6d447650',
name: 'Tenant 2',
ministryName: 'Ministry of Something',
createdDateTime: '2025-02-13T05:18:13.987Z',
updatedDateTime: '2025-02-13T05:18:13.987Z',
},
{
id: '8b802d2f-7001-47f4-ade8-b01ff868149b',
name: 'Tenant 3',
ministryName: 'Ministry of Something',
createdDateTime: '2025-02-13T05:31:41.749Z',
updatedDateTime: '2025-02-13T05:31:41.749Z',
},
],
},
};

return data.data.tenants;
} catch (e) {
errorToProblem(SERVICE, e);
return { data: { tenants: [] } };
}
}
}

const endpoint = config.get('serviceClient.commonServices.tenantManagementService.endpoint');

let tmsService = new tenantManagementService(endpoint);
module.exports = tmsService;
12 changes: 12 additions & 0 deletions app/src/forms/tms/controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const service = require('./service');

module.exports = {
getTenantsByUserId: async (req, res, next) => {
try {
const response = await service.getTenantsByUserId(req.currentUser);
res.status(200).json(response);
} catch (error) {
next(error);
}
},
};
6 changes: 6 additions & 0 deletions app/src/forms/tms/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
const routes = require('./routes');
const setupMount = require('../common/utils').setupMount;

module.exports.mount = (app) => {
return setupMount('tenant', app, routes);
};
15 changes: 15 additions & 0 deletions app/src/forms/tms/routes.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const routes = require('express').Router();
const { currentUser } = require('../auth/middleware/userAccess');
const validateParameter = require('../common/middleware/validateParameter');

const controller = require('./controller');

routes.use(currentUser);

routes.param('formId', validateParameter.validateFormId);

routes.get('/me', async (req, res, next) => {
await controller.getTenantsByUserId(req, res, next);
});

module.exports = routes;
Loading

0 comments on commit 00881bb

Please sign in to comment.