• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

GEWIS / sudosos-backend / 18598220700

17 Oct 2025 04:04PM UTC coverage: 89.772% (-0.08%) from 89.849%
18598220700

Pull #603

github

web-flow
Merge 2da62965e into 09cfd92bb
Pull Request #603: Sync controller

1367 of 1628 branches covered (83.97%)

Branch coverage included in aggregate %.

27 of 37 new or added lines in 2 files covered. (72.97%)

7199 of 7914 relevant lines covered (90.97%)

1111.25 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

72.09
/src/controller/sync-controller.ts
1
/**
2
 *  SudoSOS back-end API service.
3
 *  Copyright (C) 2024  Study association GEWIS
4
 *
5
 *  This program is free software: you can redistribute it and/or modify
6
 *  it under the terms of the GNU Affero General Public License as published
7
 *  by the Free Software Foundation, either version 3 of the License, or
8
 *  (at your option) any later version.
9
 *
10
 *  This program is distributed in the hope that it will be useful,
11
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 *  GNU Affero General Public License for more details.
14
 *
15
 *  You should have received a copy of the GNU Affero General Public License
16
 *  along with this program.  If not, see <https://www.gnu.org/licenses/>.
17
 *
18
 *  @license
19
 */
20

21
/**
22
 * This is the module page of sync-controller.
23
 *
24
 * @module internal/controllers
25
 */
26

27
import log4js from 'log4js';
2✔
28
import { Response } from 'express';
29
import { RequestWithToken } from '../middleware/token-middleware';
30
import BaseController, { BaseControllerOptions } from './base-controller';
2✔
31
import Policy from './policy';
32
import User from '../entity/user/user';
33
import UserSyncServiceFactory, { UserSyncServiceType } from '../service/sync/user/user-sync-service-factory';
2✔
34
import UserSyncManager from '../service/sync/user/user-sync-manager';
2✔
35
import { SyncResults } from '../service/sync/sync-manager';
36

37
/**
38
 * Sync controller for handling user synchronization operations.
39
 * Provides endpoints for dry-run sync operations to preview changes without applying them.
40
 */
41
export default class SyncController extends BaseController {
2✔
42
  /**
43
   * Reference to the logger instance.
44
   */
45
  private logger: log4js.Logger = log4js.getLogger('SyncController');
2✔
46

47
  /**
48
   * Creates a new sync controller instance.
49
   * @param options - The options passed to the base controller.
50
   */
51
  public constructor(options: BaseControllerOptions) {
52
    super(options);
2✔
53
    this.logger.level = process.env.LOG_LEVEL;
2✔
54
  }
55

56
  /**
57
   * @inheritDoc
58
   */
59
  public getPolicy(): Policy {
60
    return {
2✔
61
      '/user': {
62
        GET: {
63
          policy: async (req) => this.roleManager.can(
8✔
64
            req.token.roles, 'get', 'all', 'User', ['*'],
65
          ),
66
          handler: this.getUserSyncResults.bind(this),
67
        },
68
      },
69
    };
70
  }
71

72
  /**
73
   * GET /sync/user
74
   * @summary Get dry-run sync results for users
75
   * @description Performs a dry-run synchronization of users using the specified services.
76
   * This endpoint always performs a dry-run and does not apply any actual database changes.
77
   * @operationId getUserSyncResults
78
   * @tags sync - Operations of the sync controller
79
   * @security JWT
80
   * @param {Array<string>} service.query - enum:LDAP,GEWISDB - Array of sync services to use (ldap, gewisdb). If not provided, all available services will be used.
81
   * @return {object} 200 - Dry-run sync results
82
   * @return {string} 400 - Bad request (invalid service parameters)
83
   * @return {string} 500 - Internal server error
84
   */
85
  public async getUserSyncResults(req: RequestWithToken, res: Response): Promise<void> {
86
    this.logger.trace('Getting user sync results (dry-run) by', req.token.user.id);
7✔
87

88
    try {
7✔
89
      // Parse and validate service filter from query parameters
90
      let serviceFilter: UserSyncServiceType[] = [];
7✔
91
      if (req.query.service) {
7✔
92
        const services = Array.isArray(req.query.service) ? req.query.service : [req.query.service];
4✔
93
        for (let i = 0; i < services.length; i++) {
4✔
94
          if (services[i].toLowerCase === 'ldap') {
4!
NEW
95
            serviceFilter.push(UserSyncServiceType.LDAP);
×
96
          } else if (services[i].toLowerCase === 'gewisdb') {
4!
NEW
97
            serviceFilter.push(UserSyncServiceType.GEWISDB);
×
98
          } else {
99
            res.status(400).json('Invalid service: ' + services[i] + '.');
4✔
100
            return;
4✔
101
          }
102
        }
103
      }
104

105
      const syncServiceFactory = new UserSyncServiceFactory();
3✔
106
      const syncServices = syncServiceFactory.createSyncServices({
3✔
107
        roleManager: this.roleManager,
108
        serviceFilter,
109
      });
110

111
      if (syncServices.length === 0) {
3✔
112
        res.status(400).json('No sync services are available. Check environment configuration.');
3✔
113
        return;
3✔
114
      }
115

116
      // Create sync manager and run dry-run
NEW
117
      const syncManager = new UserSyncManager(syncServices);
×
NEW
118
      const results: SyncResults<User> = await syncManager.runDry();
×
119

NEW
120
      const toView = (user: User) => ({
×
121
        id: user.id,
122
        firstName: user.firstName,
123
        lastName: user.lastName,
124
        type: user.type,
125
      });
126

NEW
127
      const response = {
×
128
        users: {
129
          passed: results.passed.map(toView),
130
          failed: results.failed.map(toView),
131
          skipped: results.skipped.map(toView),
132
        },
133
      };
134

NEW
135
      this.logger.info(`Sync dry-run completed: ${results.passed.length} passed, ${results.failed.length} failed, ${results.skipped.length} skipped`);
×
NEW
136
      res.status(200).json(response);
×
137

138
    } catch (error) {
NEW
139
      this.logger.error('Error during sync dry-run:', error);
×
NEW
140
      res.status(500).json('Internal server error during sync operation.');
×
141
    }
142
  }
143
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc