mirror of
				https://github.com/Unleash/unleash.git
				synced 2025-10-27 11:02:16 +01:00 
			
		
		
		
	Merge pull request #1095 from Unleash/feat/splash
create splash migration store and service file
This commit is contained in:
		
						commit
						67f7969b84
					
				@ -29,6 +29,7 @@ import EnvironmentStore from './environment-store';
 | 
				
			|||||||
import FeatureTagStore from './feature-tag-store';
 | 
					import FeatureTagStore from './feature-tag-store';
 | 
				
			||||||
import { FeatureEnvironmentStore } from './feature-environment-store';
 | 
					import { FeatureEnvironmentStore } from './feature-environment-store';
 | 
				
			||||||
import { ClientMetricsStoreV2 } from './client-metrics-store-v2';
 | 
					import { ClientMetricsStoreV2 } from './client-metrics-store-v2';
 | 
				
			||||||
 | 
					import UserSplashStore from './user-splash-store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const createStores = (
 | 
					export const createStores = (
 | 
				
			||||||
    config: IUnleashConfig,
 | 
					    config: IUnleashConfig,
 | 
				
			||||||
@ -85,6 +86,7 @@ export const createStores = (
 | 
				
			|||||||
            eventBus,
 | 
					            eventBus,
 | 
				
			||||||
            getLogger,
 | 
					            getLogger,
 | 
				
			||||||
        ),
 | 
					        ),
 | 
				
			||||||
 | 
					        userSplashStore: new UserSplashStore(db, eventBus, getLogger),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										105
									
								
								src/lib/db/user-splash-store.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										105
									
								
								src/lib/db/user-splash-store.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,105 @@
 | 
				
			|||||||
 | 
					import { Knex } from 'knex';
 | 
				
			||||||
 | 
					import { EventEmitter } from 'events';
 | 
				
			||||||
 | 
					import { LogProvider, Logger } from '../logger';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    IUserSplash,
 | 
				
			||||||
 | 
					    IUserSplashKey,
 | 
				
			||||||
 | 
					    IUserSplashStore,
 | 
				
			||||||
 | 
					} from '../types/stores/user-splash-store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const COLUMNS = ['user_id', 'splash_id', 'seen'];
 | 
				
			||||||
 | 
					const TABLE = 'user_splash';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface IUserSplashTable {
 | 
				
			||||||
 | 
					    seen?: boolean;
 | 
				
			||||||
 | 
					    splash_id: string;
 | 
				
			||||||
 | 
					    user_id: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const fieldToRow = (fields: IUserSplash): IUserSplashTable => ({
 | 
				
			||||||
 | 
					    seen: fields.seen,
 | 
				
			||||||
 | 
					    splash_id: fields.splashId,
 | 
				
			||||||
 | 
					    user_id: fields.userId,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const rowToField = (row: IUserSplashTable): IUserSplash => ({
 | 
				
			||||||
 | 
					    seen: row.seen,
 | 
				
			||||||
 | 
					    splashId: row.splash_id,
 | 
				
			||||||
 | 
					    userId: row.user_id,
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class UserSplashStore implements IUserSplashStore {
 | 
				
			||||||
 | 
					    private db: Knex;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private logger: Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(db: Knex, eventBus: EventEmitter, getLogger: LogProvider) {
 | 
				
			||||||
 | 
					        this.db = db;
 | 
				
			||||||
 | 
					        this.logger = getLogger('user-splash-store.ts');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async getAllUserSplashs(userId: number): Promise<IUserSplash[]> {
 | 
				
			||||||
 | 
					        const userSplash = await this.db
 | 
				
			||||||
 | 
					            .table<IUserSplashTable>(TABLE)
 | 
				
			||||||
 | 
					            .select()
 | 
				
			||||||
 | 
					            .where({ user_id: userId });
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return userSplash.map(rowToField);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async getSplash(userId: number, splashId: string): Promise<IUserSplash> {
 | 
				
			||||||
 | 
					        const userSplash = await this.db
 | 
				
			||||||
 | 
					            .table<IUserSplashTable>(TABLE)
 | 
				
			||||||
 | 
					            .select()
 | 
				
			||||||
 | 
					            .where({ user_id: userId, splash_id: splashId })
 | 
				
			||||||
 | 
					            .first();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return rowToField(userSplash);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async updateSplash(splash: IUserSplash): Promise<IUserSplash> {
 | 
				
			||||||
 | 
					        const insertedSplash = await this.db
 | 
				
			||||||
 | 
					            .table<IUserSplashTable>(TABLE)
 | 
				
			||||||
 | 
					            .insert(fieldToRow(splash))
 | 
				
			||||||
 | 
					            .onConflict(['user_id', 'splash_id'])
 | 
				
			||||||
 | 
					            .merge()
 | 
				
			||||||
 | 
					            .returning(COLUMNS);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return rowToField(insertedSplash[0]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async delete({ userId, splashId }: IUserSplashKey): Promise<void> {
 | 
				
			||||||
 | 
					        await this.db(TABLE)
 | 
				
			||||||
 | 
					            .where({ user_id: userId, splash_id: splashId })
 | 
				
			||||||
 | 
					            .del();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async deleteAll(): Promise<void> {
 | 
				
			||||||
 | 
					        await this.db(TABLE).del();
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    destroy(): void {}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async exists({ userId, splashId }: IUserSplashKey): Promise<boolean> {
 | 
				
			||||||
 | 
					        const result = await this.db.raw(
 | 
				
			||||||
 | 
					            `SELECT EXISTS (SELECT 1 FROM ${TABLE} WHERE user_id = ? AND splash_id = ?) AS present`,
 | 
				
			||||||
 | 
					            [userId, splashId],
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					        const { present } = result.rows[0];
 | 
				
			||||||
 | 
					        return present;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async get({ userId, splashId }: IUserSplashKey): Promise<IUserSplash> {
 | 
				
			||||||
 | 
					        return this.getSplash(userId, splashId);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async getAll(): Promise<IUserSplash[]> {
 | 
				
			||||||
 | 
					        const userSplashs = await this.db
 | 
				
			||||||
 | 
					            .table<IUserSplashTable>(TABLE)
 | 
				
			||||||
 | 
					            .select();
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        return userSplashs.map(rowToField);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = UserSplashStore;
 | 
				
			||||||
@ -21,6 +21,7 @@ import ApiTokenController from './api-token-controller';
 | 
				
			|||||||
import UserAdminController from './user-admin';
 | 
					import UserAdminController from './user-admin';
 | 
				
			||||||
import EmailController from './email';
 | 
					import EmailController from './email';
 | 
				
			||||||
import UserFeedbackController from './user-feedback-controller';
 | 
					import UserFeedbackController from './user-feedback-controller';
 | 
				
			||||||
 | 
					import UserSplashController from './user-splash-controller';
 | 
				
			||||||
import ProjectApi from './project';
 | 
					import ProjectApi from './project';
 | 
				
			||||||
import { EnvironmentsController } from './environments-controller';
 | 
					import { EnvironmentsController } from './environments-controller';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@ -92,6 +93,10 @@ class AdminApi extends Controller {
 | 
				
			|||||||
            '/environments',
 | 
					            '/environments',
 | 
				
			||||||
            new EnvironmentsController(config, services).router,
 | 
					            new EnvironmentsController(config, services).router,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					        this.app.use(
 | 
				
			||||||
 | 
					            '/splash',
 | 
				
			||||||
 | 
					            new UserSplashController(config, services).router,
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    index(req, res) {
 | 
					    index(req, res) {
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										49
									
								
								src/lib/routes/admin-api/user-splash-controller.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								src/lib/routes/admin-api/user-splash-controller.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,49 @@
 | 
				
			|||||||
 | 
					import { Response } from 'express';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import Controller from '../controller';
 | 
				
			||||||
 | 
					import { Logger } from '../../logger';
 | 
				
			||||||
 | 
					import { IUnleashConfig } from '../../types/option';
 | 
				
			||||||
 | 
					import { IUnleashServices } from '../../types/services';
 | 
				
			||||||
 | 
					import UserSplashService from '../../services/user-splash-service';
 | 
				
			||||||
 | 
					import { IAuthRequest } from '../unleash-types';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					interface ISplashBody {
 | 
				
			||||||
 | 
					    seen: boolean;
 | 
				
			||||||
 | 
					    splashId: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class UserSplashController extends Controller {
 | 
				
			||||||
 | 
					    private logger: Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private userSplashService: UserSplashService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(
 | 
				
			||||||
 | 
					        config: IUnleashConfig,
 | 
				
			||||||
 | 
					        { userSplashService }: Pick<IUnleashServices, 'userSplashService'>,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        super(config);
 | 
				
			||||||
 | 
					        this.logger = config.getLogger('splash-controller.ts');
 | 
				
			||||||
 | 
					        this.userSplashService = userSplashService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        this.post('/:id', this.updateSplashSettings);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private async updateSplashSettings(
 | 
				
			||||||
 | 
					        req: IAuthRequest<any, any, ISplashBody, any>,
 | 
				
			||||||
 | 
					        res: Response,
 | 
				
			||||||
 | 
					    ): Promise<void> {
 | 
				
			||||||
 | 
					        const { user } = req;
 | 
				
			||||||
 | 
					        const { id } = req.params;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        const splash = {
 | 
				
			||||||
 | 
					            splashId: id,
 | 
				
			||||||
 | 
					            userId: user.id,
 | 
				
			||||||
 | 
					            seen: true,
 | 
				
			||||||
 | 
					        };
 | 
				
			||||||
 | 
					        const updated = await this.userSplashService.updateSplash(splash);
 | 
				
			||||||
 | 
					        res.json(updated);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = UserSplashController;
 | 
				
			||||||
 | 
					export default UserSplashController;
 | 
				
			||||||
@ -7,6 +7,7 @@ import { IUnleashServices } from '../../types/services';
 | 
				
			|||||||
import UserService from '../../services/user-service';
 | 
					import UserService from '../../services/user-service';
 | 
				
			||||||
import SessionService from '../../services/session-service';
 | 
					import SessionService from '../../services/session-service';
 | 
				
			||||||
import UserFeedbackService from '../../services/user-feedback-service';
 | 
					import UserFeedbackService from '../../services/user-feedback-service';
 | 
				
			||||||
 | 
					import UserSplashService from '../../services/user-splash-service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
interface IChangeUserRequest {
 | 
					interface IChangeUserRequest {
 | 
				
			||||||
    password: string;
 | 
					    password: string;
 | 
				
			||||||
@ -22,6 +23,8 @@ class UserController extends Controller {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    private sessionService: SessionService;
 | 
					    private sessionService: SessionService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private userSplashService: UserSplashService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    constructor(
 | 
					    constructor(
 | 
				
			||||||
        config: IUnleashConfig,
 | 
					        config: IUnleashConfig,
 | 
				
			||||||
        {
 | 
					        {
 | 
				
			||||||
@ -29,12 +32,14 @@ class UserController extends Controller {
 | 
				
			|||||||
            userService,
 | 
					            userService,
 | 
				
			||||||
            sessionService,
 | 
					            sessionService,
 | 
				
			||||||
            userFeedbackService,
 | 
					            userFeedbackService,
 | 
				
			||||||
 | 
					            userSplashService,
 | 
				
			||||||
        }: Pick<
 | 
					        }: Pick<
 | 
				
			||||||
            IUnleashServices,
 | 
					            IUnleashServices,
 | 
				
			||||||
            | 'accessService'
 | 
					            | 'accessService'
 | 
				
			||||||
            | 'userService'
 | 
					            | 'userService'
 | 
				
			||||||
            | 'sessionService'
 | 
					            | 'sessionService'
 | 
				
			||||||
            | 'userFeedbackService'
 | 
					            | 'userFeedbackService'
 | 
				
			||||||
 | 
					            | 'userSplashService'
 | 
				
			||||||
        >,
 | 
					        >,
 | 
				
			||||||
    ) {
 | 
					    ) {
 | 
				
			||||||
        super(config);
 | 
					        super(config);
 | 
				
			||||||
@ -42,6 +47,7 @@ class UserController extends Controller {
 | 
				
			|||||||
        this.userService = userService;
 | 
					        this.userService = userService;
 | 
				
			||||||
        this.sessionService = sessionService;
 | 
					        this.sessionService = sessionService;
 | 
				
			||||||
        this.userFeedbackService = userFeedbackService;
 | 
					        this.userFeedbackService = userFeedbackService;
 | 
				
			||||||
 | 
					        this.userSplashService = userSplashService;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        this.get('/', this.getUser);
 | 
					        this.get('/', this.getUser);
 | 
				
			||||||
        this.post('/change-password', this.updateUserPass);
 | 
					        this.post('/change-password', this.updateUserPass);
 | 
				
			||||||
@ -57,11 +63,15 @@ class UserController extends Controller {
 | 
				
			|||||||
        const feedback = await this.userFeedbackService.getAllUserFeedback(
 | 
					        const feedback = await this.userFeedbackService.getAllUserFeedback(
 | 
				
			||||||
            user,
 | 
					            user,
 | 
				
			||||||
        );
 | 
					        );
 | 
				
			||||||
 | 
					        const splash = await this.userSplashService.getAllUserSplashs(user);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        // TODO: remove this line after we remove it from db.
 | 
					        // TODO: remove this line after we remove it from db.
 | 
				
			||||||
        delete user.permissions;
 | 
					        delete user.permissions;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return res.status(200).json({ user, permissions, feedback }).end();
 | 
					        return res
 | 
				
			||||||
 | 
					            .status(200)
 | 
				
			||||||
 | 
					            .json({ user, permissions, feedback, splash })
 | 
				
			||||||
 | 
					            .end();
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    async updateUserPass(
 | 
					    async updateUserPass(
 | 
				
			||||||
 | 
				
			|||||||
@ -27,6 +27,7 @@ import FeatureToggleService from './feature-toggle-service';
 | 
				
			|||||||
import EnvironmentService from './environment-service';
 | 
					import EnvironmentService from './environment-service';
 | 
				
			||||||
import FeatureTagService from './feature-tag-service';
 | 
					import FeatureTagService from './feature-tag-service';
 | 
				
			||||||
import ProjectHealthService from './project-health-service';
 | 
					import ProjectHealthService from './project-health-service';
 | 
				
			||||||
 | 
					import UserSplashService from './user-splash-service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const createServices = (
 | 
					export const createServices = (
 | 
				
			||||||
    stores: IUnleashStores,
 | 
					    stores: IUnleashStores,
 | 
				
			||||||
@ -72,6 +73,7 @@ export const createServices = (
 | 
				
			|||||||
        accessService,
 | 
					        accessService,
 | 
				
			||||||
        featureToggleServiceV2,
 | 
					        featureToggleServiceV2,
 | 
				
			||||||
    );
 | 
					    );
 | 
				
			||||||
 | 
					    const userSplashService = new UserSplashService(stores, config);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    return {
 | 
					    return {
 | 
				
			||||||
        accessService,
 | 
					        accessService,
 | 
				
			||||||
@ -100,6 +102,7 @@ export const createServices = (
 | 
				
			|||||||
        userFeedbackService,
 | 
					        userFeedbackService,
 | 
				
			||||||
        featureTagService,
 | 
					        featureTagService,
 | 
				
			||||||
        projectHealthService,
 | 
					        projectHealthService,
 | 
				
			||||||
 | 
					        userSplashService,
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										54
									
								
								src/lib/services/user-splash-service.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										54
									
								
								src/lib/services/user-splash-service.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,54 @@
 | 
				
			|||||||
 | 
					import { Logger } from '../logger';
 | 
				
			||||||
 | 
					import { IUnleashStores } from '../types/stores';
 | 
				
			||||||
 | 
					import { IUnleashConfig } from '../types/option';
 | 
				
			||||||
 | 
					import User from '../types/user';
 | 
				
			||||||
 | 
					import {
 | 
				
			||||||
 | 
					    IUserSplash,
 | 
				
			||||||
 | 
					    IUserSplashStore,
 | 
				
			||||||
 | 
					} from '../types/stores/user-splash-store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class UserSplashService {
 | 
				
			||||||
 | 
					    private userSplashStore: IUserSplashStore;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    private logger: Logger;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    constructor(
 | 
				
			||||||
 | 
					        { userSplashStore }: Pick<IUnleashStores, 'userSplashStore'>,
 | 
				
			||||||
 | 
					        { getLogger }: Pick<IUnleashConfig, 'getLogger'>,
 | 
				
			||||||
 | 
					    ) {
 | 
				
			||||||
 | 
					        this.userSplashStore = userSplashStore;
 | 
				
			||||||
 | 
					        this.logger = getLogger('services/user-splash-service.js');
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async getAllUserSplashs(user: User): Promise<Object> {
 | 
				
			||||||
 | 
					        if (user.isAPI) {
 | 
				
			||||||
 | 
					            return [];
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					        try {
 | 
				
			||||||
 | 
					            const splashs = (
 | 
				
			||||||
 | 
					                await this.userSplashStore.getAllUserSplashs(user.id)
 | 
				
			||||||
 | 
					            ).reduce(
 | 
				
			||||||
 | 
					                (splashObject, splash) => ({
 | 
				
			||||||
 | 
					                    ...splashObject,
 | 
				
			||||||
 | 
					                    [splash.splashId]: splash.seen,
 | 
				
			||||||
 | 
					                }),
 | 
				
			||||||
 | 
					                {},
 | 
				
			||||||
 | 
					            );
 | 
				
			||||||
 | 
					            return splashs;
 | 
				
			||||||
 | 
					        } catch (err) {
 | 
				
			||||||
 | 
					            this.logger.error(err);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            return {};
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async getSplash(user_id: number, splash_id: string): Promise<IUserSplash> {
 | 
				
			||||||
 | 
					        return this.userSplashStore.getSplash(user_id, splash_id);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    async updateSplash(splash: IUserSplash): Promise<IUserSplash> {
 | 
				
			||||||
 | 
					        return this.userSplashStore.updateSplash(splash);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module.exports = UserSplashService;
 | 
				
			||||||
@ -23,6 +23,7 @@ import EnvironmentService from '../services/environment-service';
 | 
				
			|||||||
import FeatureTagService from '../services/feature-tag-service';
 | 
					import FeatureTagService from '../services/feature-tag-service';
 | 
				
			||||||
import ProjectHealthService from '../services/project-health-service';
 | 
					import ProjectHealthService from '../services/project-health-service';
 | 
				
			||||||
import ClientMetricsServiceV2 from '../services/client-metrics/client-metrics-service-v2';
 | 
					import ClientMetricsServiceV2 from '../services/client-metrics/client-metrics-service-v2';
 | 
				
			||||||
 | 
					import UserSplashService from '../services/user-splash-service';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IUnleashServices {
 | 
					export interface IUnleashServices {
 | 
				
			||||||
    accessService: AccessService;
 | 
					    accessService: AccessService;
 | 
				
			||||||
@ -51,4 +52,5 @@ export interface IUnleashServices {
 | 
				
			|||||||
    userFeedbackService: UserFeedbackService;
 | 
					    userFeedbackService: UserFeedbackService;
 | 
				
			||||||
    userService: UserService;
 | 
					    userService: UserService;
 | 
				
			||||||
    versionService: VersionService;
 | 
					    versionService: VersionService;
 | 
				
			||||||
 | 
					    userSplashService: UserSplashService;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
@ -23,6 +23,7 @@ import { IFeatureStrategiesStore } from './stores/feature-strategies-store';
 | 
				
			|||||||
import { IEnvironmentStore } from './stores/environment-store';
 | 
					import { IEnvironmentStore } from './stores/environment-store';
 | 
				
			||||||
import { IFeatureToggleClientStore } from './stores/feature-toggle-client-store';
 | 
					import { IFeatureToggleClientStore } from './stores/feature-toggle-client-store';
 | 
				
			||||||
import { IClientMetricsStoreV2 } from './stores/client-metrics-store-v2';
 | 
					import { IClientMetricsStoreV2 } from './stores/client-metrics-store-v2';
 | 
				
			||||||
 | 
					import { IUserSplashStore } from './stores/user-splash-store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export interface IUnleashStores {
 | 
					export interface IUnleashStores {
 | 
				
			||||||
    accessStore: IAccessStore;
 | 
					    accessStore: IAccessStore;
 | 
				
			||||||
@ -50,4 +51,5 @@ export interface IUnleashStores {
 | 
				
			|||||||
    tagTypeStore: ITagTypeStore;
 | 
					    tagTypeStore: ITagTypeStore;
 | 
				
			||||||
    userFeedbackStore: IUserFeedbackStore;
 | 
					    userFeedbackStore: IUserFeedbackStore;
 | 
				
			||||||
    userStore: IUserStore;
 | 
					    userStore: IUserStore;
 | 
				
			||||||
 | 
					    userSplashStore: IUserSplashStore;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
				
			|||||||
							
								
								
									
										18
									
								
								src/lib/types/stores/user-splash-store.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								src/lib/types/stores/user-splash-store.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,18 @@
 | 
				
			|||||||
 | 
					import { Store } from './store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IUserSplash {
 | 
				
			||||||
 | 
					    seen: boolean;
 | 
				
			||||||
 | 
					    splashId: string;
 | 
				
			||||||
 | 
					    userId: number;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IUserSplashKey {
 | 
				
			||||||
 | 
					    userId: number;
 | 
				
			||||||
 | 
					    splashId: string;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export interface IUserSplashStore extends Store<IUserSplash, IUserSplashKey> {
 | 
				
			||||||
 | 
					    getAllUserSplashs(userId: number): Promise<IUserSplash[]>;
 | 
				
			||||||
 | 
					    getSplash(userId: number, splashId: string): Promise<IUserSplash>;
 | 
				
			||||||
 | 
					    updateSplash(splash: IUserSplash): Promise<IUserSplash>;
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										25
									
								
								src/migrations/20211108130333-create-user-splash-table.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/migrations/20211108130333-create-user-splash-table.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
				
			|||||||
 | 
					'use strict';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports.up = function (db, cb) {
 | 
				
			||||||
 | 
					    db.runSql(
 | 
				
			||||||
 | 
					        `
 | 
				
			||||||
 | 
					        CREATE TABLE IF NOT EXISTS user_splash 
 | 
				
			||||||
 | 
					        (user_id INTEGER NOT NULL references users (id) ON DELETE CASCADE, 
 | 
				
			||||||
 | 
					        splash_id TEXT, 
 | 
				
			||||||
 | 
					        seen BOOLEAN NOT NULL DEFAULT false, 
 | 
				
			||||||
 | 
					        PRIMARY KEY (user_id, splash_id));
 | 
				
			||||||
 | 
					        CREATE INDEX user_splash_user_id_idx ON user_splash (user_id);
 | 
				
			||||||
 | 
					        `,
 | 
				
			||||||
 | 
					        cb,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports.down = function (db, cb) {
 | 
				
			||||||
 | 
					    db.runSql(
 | 
				
			||||||
 | 
					        `
 | 
				
			||||||
 | 
					    DROP INDEX user_splash_user_id_idx;
 | 
				
			||||||
 | 
					    DROP TABLE user_splash;
 | 
				
			||||||
 | 
					`,
 | 
				
			||||||
 | 
					        cb,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										10
									
								
								src/migrations/20211109103930-add-splash-entry-for-users.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										10
									
								
								src/migrations/20211109103930-add-splash-entry-for-users.js
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,10 @@
 | 
				
			|||||||
 | 
					exports.up = function (db, cb) {
 | 
				
			||||||
 | 
					    db.runSql(
 | 
				
			||||||
 | 
					        `INSERT INTO user_splash(splash_id, user_id, seen) SELECT 'environment', u.id, false FROM users u`,
 | 
				
			||||||
 | 
					        cb,
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					exports.down = function (db, cb) {
 | 
				
			||||||
 | 
					    db.runSql('DELETE FROM user_splash', cb);
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
							
								
								
									
										68
									
								
								src/test/e2e/api/admin/splash.e2e.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										68
									
								
								src/test/e2e/api/admin/splash.e2e.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,68 @@
 | 
				
			|||||||
 | 
					import { Application, NextFunction, Request, Response } from 'express';
 | 
				
			||||||
 | 
					import { setupAppWithCustomAuth } from '../../helpers/test-helper';
 | 
				
			||||||
 | 
					import dbInit from '../../helpers/database-init';
 | 
				
			||||||
 | 
					import getLogger from '../../../fixtures/no-logger';
 | 
				
			||||||
 | 
					import { IUnleashConfig } from '../../../../lib/types/option';
 | 
				
			||||||
 | 
					import { IUnleashServices } from '../../../../lib/types/services';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let stores;
 | 
				
			||||||
 | 
					let db;
 | 
				
			||||||
 | 
					let app;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					beforeAll(async () => {
 | 
				
			||||||
 | 
					    db = await dbInit('splash_api_serial', getLogger);
 | 
				
			||||||
 | 
					    stores = db.stores;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const email = 'custom-user@mail.com';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    const preHook = (
 | 
				
			||||||
 | 
					        application: Application,
 | 
				
			||||||
 | 
					        config: IUnleashConfig,
 | 
				
			||||||
 | 
					        { userService }: IUnleashServices,
 | 
				
			||||||
 | 
					    ) => {
 | 
				
			||||||
 | 
					        application.use(
 | 
				
			||||||
 | 
					            '/api/admin/',
 | 
				
			||||||
 | 
					            async (req: Request, res: Response, next: NextFunction) => {
 | 
				
			||||||
 | 
					                // @ts-ignore
 | 
				
			||||||
 | 
					                req.user = await userService.loginUserWithoutPassword(
 | 
				
			||||||
 | 
					                    email,
 | 
				
			||||||
 | 
					                    true,
 | 
				
			||||||
 | 
					                );
 | 
				
			||||||
 | 
					                next();
 | 
				
			||||||
 | 
					            },
 | 
				
			||||||
 | 
					        );
 | 
				
			||||||
 | 
					    };
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    app = await setupAppWithCustomAuth(stores, preHook);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					afterAll(async () => {
 | 
				
			||||||
 | 
					    await app.destroy();
 | 
				
			||||||
 | 
					    await db.destroy();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('it updates splash for user', async () => {
 | 
				
			||||||
 | 
					    expect.assertions(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return app.request
 | 
				
			||||||
 | 
					        .post('/api/admin/splash/environment')
 | 
				
			||||||
 | 
					        .set('Content-Type', 'application/json')
 | 
				
			||||||
 | 
					        .expect('Content-Type', /json/)
 | 
				
			||||||
 | 
					        .expect(200)
 | 
				
			||||||
 | 
					        .expect((res) => {
 | 
				
			||||||
 | 
					            expect(res.body.seen).toBe(true);
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('it retrieves splash for user', async () => {
 | 
				
			||||||
 | 
					    expect.assertions(1);
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    return app.request
 | 
				
			||||||
 | 
					        .get('/api/admin/user')
 | 
				
			||||||
 | 
					        .set('Content-Type', 'application/json')
 | 
				
			||||||
 | 
					        .expect('Content-Type', /json/)
 | 
				
			||||||
 | 
					        .expect(200)
 | 
				
			||||||
 | 
					        .expect((res) => {
 | 
				
			||||||
 | 
					            expect(res.body.splash).toStrictEqual({ environment: true });
 | 
				
			||||||
 | 
					        });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										82
									
								
								src/test/e2e/stores/user-splash-store.e2e.test.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										82
									
								
								src/test/e2e/stores/user-splash-store.e2e.test.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,82 @@
 | 
				
			|||||||
 | 
					import { IUserSplashStore } from 'lib/types/stores/user-splash-store';
 | 
				
			||||||
 | 
					import { IUserStore } from 'lib/types/stores/user-store';
 | 
				
			||||||
 | 
					import dbInit from '../helpers/database-init';
 | 
				
			||||||
 | 
					import getLogger from '../../fixtures/no-logger';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					let stores;
 | 
				
			||||||
 | 
					let db;
 | 
				
			||||||
 | 
					let userSplashStore: IUserSplashStore;
 | 
				
			||||||
 | 
					let userStore: IUserStore;
 | 
				
			||||||
 | 
					let currentUser;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					beforeAll(async () => {
 | 
				
			||||||
 | 
					    db = await dbInit('user_splash_store', getLogger);
 | 
				
			||||||
 | 
					    stores = db.stores;
 | 
				
			||||||
 | 
					    userSplashStore = stores.userSplashStore;
 | 
				
			||||||
 | 
					    userStore = stores.userStore;
 | 
				
			||||||
 | 
					    currentUser = await userStore.upsert({ email: 'me.feedback@mail.com' });
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					afterAll(async () => {
 | 
				
			||||||
 | 
					    await db.destroy();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					afterEach(async () => {
 | 
				
			||||||
 | 
					    await userSplashStore.deleteAll();
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('should create userSplash', async () => {
 | 
				
			||||||
 | 
					    await userSplashStore.updateSplash({
 | 
				
			||||||
 | 
					        splashId: 'some-id',
 | 
				
			||||||
 | 
					        userId: currentUser.id,
 | 
				
			||||||
 | 
					        seen: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const userSplashs = await userSplashStore.getAllUserSplashs(currentUser.id);
 | 
				
			||||||
 | 
					    expect(userSplashs).toHaveLength(1);
 | 
				
			||||||
 | 
					    expect(userSplashs[0].splashId).toBe('some-id');
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('should get userSplash', async () => {
 | 
				
			||||||
 | 
					    await userSplashStore.updateSplash({
 | 
				
			||||||
 | 
					        splashId: 'some-id',
 | 
				
			||||||
 | 
					        userId: currentUser.id,
 | 
				
			||||||
 | 
					        seen: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const userSplash = await userSplashStore.getSplash(
 | 
				
			||||||
 | 
					        currentUser.id,
 | 
				
			||||||
 | 
					        'some-id',
 | 
				
			||||||
 | 
					    );
 | 
				
			||||||
 | 
					    expect(userSplash.splashId).toBe('some-id');
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('should exists', async () => {
 | 
				
			||||||
 | 
					    await userSplashStore.updateSplash({
 | 
				
			||||||
 | 
					        splashId: 'some-id-3',
 | 
				
			||||||
 | 
					        userId: currentUser.id,
 | 
				
			||||||
 | 
					        seen: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const exists = await userSplashStore.exists({
 | 
				
			||||||
 | 
					        userId: currentUser.id,
 | 
				
			||||||
 | 
					        splashId: 'some-id-3',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(exists).toBe(true);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('should not exists', async () => {
 | 
				
			||||||
 | 
					    const exists = await userSplashStore.exists({
 | 
				
			||||||
 | 
					        userId: currentUser.id,
 | 
				
			||||||
 | 
					        splashId: 'some-id-not-here',
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    expect(exists).toBe(false);
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					test('should get all userSplashs', async () => {
 | 
				
			||||||
 | 
					    await userSplashStore.updateSplash({
 | 
				
			||||||
 | 
					        splashId: 'some-id-2',
 | 
				
			||||||
 | 
					        userId: currentUser.id,
 | 
				
			||||||
 | 
					        seen: false,
 | 
				
			||||||
 | 
					    });
 | 
				
			||||||
 | 
					    const userSplashs = await userSplashStore.getAll();
 | 
				
			||||||
 | 
					    expect(userSplashs).toHaveLength(1);
 | 
				
			||||||
 | 
					    expect(userSplashs[0].splashId).toBe('some-id-2');
 | 
				
			||||||
 | 
					});
 | 
				
			||||||
							
								
								
									
										50
									
								
								src/test/fixtures/fake-user-splash-store.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										50
									
								
								src/test/fixtures/fake-user-splash-store.ts
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@ -0,0 +1,50 @@
 | 
				
			|||||||
 | 
					import {
 | 
				
			||||||
 | 
					    IUserSplashKey,
 | 
				
			||||||
 | 
					    IUserSplash,
 | 
				
			||||||
 | 
					    IUserSplashStore,
 | 
				
			||||||
 | 
					} from '../../lib/types/stores/user-splash-store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					export default class FakeUserSplashStore implements IUserSplashStore {
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					    getAllUserSplashs(userId: number): Promise<IUserSplash[]> {
 | 
				
			||||||
 | 
					        return Promise.resolve([]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					    getSplash(userId: number, splashId: string): Promise<IUserSplash> {
 | 
				
			||||||
 | 
					        return Promise.resolve(undefined);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					    updateSplash(splash: IUserSplash): Promise<IUserSplash> {
 | 
				
			||||||
 | 
					        return Promise.resolve(undefined);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					    exists(key: IUserSplashKey): Promise<boolean> {
 | 
				
			||||||
 | 
					        return Promise.resolve(false);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					    get(key: IUserSplashKey): Promise<IUserSplash> {
 | 
				
			||||||
 | 
					        return Promise.resolve(undefined);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					    getAll(): Promise<IUserSplash[]> {
 | 
				
			||||||
 | 
					        return Promise.resolve([]);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					    delete(key: IUserSplashKey): Promise<void> {
 | 
				
			||||||
 | 
					        return Promise.resolve(undefined);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					    deleteAll(): Promise<void> {
 | 
				
			||||||
 | 
					        return Promise.resolve(undefined);
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | 
				
			||||||
 | 
					    destroy(): void {}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										2
									
								
								src/test/fixtures/store.ts
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								src/test/fixtures/store.ts
									
									
									
									
										vendored
									
									
								
							@ -24,6 +24,7 @@ import FakeFeatureTypeStore from './fake-feature-type-store';
 | 
				
			|||||||
import FakeResetTokenStore from './fake-reset-token-store';
 | 
					import FakeResetTokenStore from './fake-reset-token-store';
 | 
				
			||||||
import FakeFeatureToggleClientStore from './fake-feature-toggle-client-store';
 | 
					import FakeFeatureToggleClientStore from './fake-feature-toggle-client-store';
 | 
				
			||||||
import FakeClientMetricsStoreV2 from './fake-client-metrics-store-v2';
 | 
					import FakeClientMetricsStoreV2 from './fake-client-metrics-store-v2';
 | 
				
			||||||
 | 
					import FakeUserSplashStore from './fake-user-splash-store';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const createStores: () => IUnleashStores = () => {
 | 
					const createStores: () => IUnleashStores = () => {
 | 
				
			||||||
    const db = {
 | 
					    const db = {
 | 
				
			||||||
@ -59,6 +60,7 @@ const createStores: () => IUnleashStores = () => {
 | 
				
			|||||||
        featureTypeStore: new FakeFeatureTypeStore(),
 | 
					        featureTypeStore: new FakeFeatureTypeStore(),
 | 
				
			||||||
        resetTokenStore: new FakeResetTokenStore(),
 | 
					        resetTokenStore: new FakeResetTokenStore(),
 | 
				
			||||||
        sessionStore: new FakeSessionStore(),
 | 
					        sessionStore: new FakeSessionStore(),
 | 
				
			||||||
 | 
					        userSplashStore: new FakeUserSplashStore(),
 | 
				
			||||||
    };
 | 
					    };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
				
			|||||||
		Loading…
	
		Reference in New Issue
	
	Block a user