diff --git a/apps/judicial-system/api/src/app/modules/backend/backend.service.ts b/apps/judicial-system/api/src/app/modules/backend/backend.service.ts index ba82a1cb9745..238f4b51de76 100644 --- a/apps/judicial-system/api/src/app/modules/backend/backend.service.ts +++ b/apps/judicial-system/api/src/app/modules/backend/backend.service.ts @@ -15,9 +15,12 @@ import { SignatureConfirmationResponse, } from '../case' import { CaseListEntry } from '../case-list' -import { Defendant, DeleteDefendantResponse } from '../defendant' -import { CivilClaimant } from '../defendant/models/civilClaimant.model' -import { DeleteCivilClaimantResponse } from '../defendant/models/deleteCivilClaimant.response' +import { + CivilClaimant, + Defendant, + DeleteCivilClaimantResponse, + DeleteDefendantResponse, +} from '../defendant' import { CreateEventLogInput } from '../event-log' import { CaseFile, diff --git a/apps/judicial-system/api/src/app/modules/case/models/case.model.ts b/apps/judicial-system/api/src/app/modules/case/models/case.model.ts index 80f811efa64b..a4e9e9778078 100644 --- a/apps/judicial-system/api/src/app/modules/case/models/case.model.ts +++ b/apps/judicial-system/api/src/app/modules/case/models/case.model.ts @@ -26,8 +26,7 @@ import { UserRole, } from '@island.is/judicial-system/types' -import { Defendant } from '../../defendant' -import { CivilClaimant } from '../../defendant/models/civilClaimant.model' +import { CivilClaimant, Defendant } from '../../defendant' import { EventLog } from '../../event-log' import { CaseFile } from '../../file' import { IndictmentCount } from '../../indictment-count' diff --git a/apps/judicial-system/api/src/app/modules/defendant/index.ts b/apps/judicial-system/api/src/app/modules/defendant/index.ts index b6c4df49e25e..0811956a0ca8 100644 --- a/apps/judicial-system/api/src/app/modules/defendant/index.ts +++ b/apps/judicial-system/api/src/app/modules/defendant/index.ts @@ -1,2 +1,4 @@ export { Defendant } from './models/defendant.model' export { DeleteDefendantResponse } from './models/delete.response' +export { CivilClaimant } from './models/civilClaimant.model' +export { DeleteCivilClaimantResponse } from './models/deleteCivilClaimant.response' diff --git a/apps/judicial-system/backend/src/app/modules/case/case.service.ts b/apps/judicial-system/backend/src/app/modules/case/case.service.ts index 878a7147d6b4..07f1c57d1012 100644 --- a/apps/judicial-system/backend/src/app/modules/case/case.service.ts +++ b/apps/judicial-system/backend/src/app/modules/case/case.service.ts @@ -55,8 +55,7 @@ import { } from '../../formatters' import { AwsS3Service } from '../aws-s3' import { CourtService } from '../court' -import { Defendant, DefendantService } from '../defendant' -import { CivilClaimant } from '../defendant/models/civilClaimant.model' +import { CivilClaimant, Defendant, DefendantService } from '../defendant' import { EventService } from '../event' import { EventLog, EventLogService } from '../event-log' import { CaseFile, FileService } from '../file' @@ -396,6 +395,7 @@ export const caseListInclude: Includeable[] = [ export const listOrder: OrderItem[] = [ [{ model: Defendant, as: 'defendants' }, 'created', 'ASC'], + [{ model: CivilClaimant, as: 'civilClaimants' }, 'created', 'ASC'], [{ model: DateLog, as: 'dateLogs' }, 'created', 'DESC'], ] diff --git a/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts b/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts index 3a163ee3a339..96f58bc55d26 100644 --- a/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts +++ b/apps/judicial-system/backend/src/app/modules/case/models/case.model.ts @@ -37,8 +37,7 @@ import { UserRole, } from '@island.is/judicial-system/types' -import { Defendant } from '../../defendant' -import { CivilClaimant } from '../../defendant/models/civilClaimant.model' +import { CivilClaimant, Defendant } from '../../defendant' import { EventLog } from '../../event-log' import { CaseFile } from '../../file' import { IndictmentCount } from '../../indictment-count' diff --git a/apps/judicial-system/backend/src/app/modules/defendant/index.ts b/apps/judicial-system/backend/src/app/modules/defendant/index.ts index ee604a0d6779..3839cdb96469 100644 --- a/apps/judicial-system/backend/src/app/modules/defendant/index.ts +++ b/apps/judicial-system/backend/src/app/modules/defendant/index.ts @@ -1,2 +1,3 @@ export { Defendant } from './models/defendant.model' export { DefendantService } from './defendant.service' +export { CivilClaimant } from './models/civilClaimant.model' diff --git a/apps/services/endorsements/api/migrations/20240904000001-endorsement-count.js b/apps/services/endorsements/api/migrations/20240904000001-endorsement-count.js new file mode 100644 index 000000000000..98b67c2fdf1c --- /dev/null +++ b/apps/services/endorsements/api/migrations/20240904000001-endorsement-count.js @@ -0,0 +1,30 @@ +module.exports = { + up: async (queryInterface, Sequelize) => { + // Add the 'endorsement_count' column to 'endorsement_list' table + await queryInterface.addColumn('endorsement_list', 'endorsement_count', { + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 0, + }) + + // Add composite index on 'endorsement_count' and 'counter' + await queryInterface.addIndex( + 'endorsement_list', + ['endorsement_count', 'counter'], + { + name: 'idx_endorsement_count_counter', + }, + ) + }, + + down: async (queryInterface, Sequelize) => { + // Remove the composite index on 'endorsement_count' and 'counter' + await queryInterface.removeIndex( + 'endorsement_list', + 'idx_endorsement_count_counter', + ) + + // Remove the 'endorsement_count' column from 'endorsement_list' table + await queryInterface.removeColumn('endorsement_list', 'endorsement_count') + }, +} diff --git a/apps/services/endorsements/api/src/app/modules/endorsement/endorsement.service.ts b/apps/services/endorsements/api/src/app/modules/endorsement/endorsement.service.ts index d6dc2d6a2376..cafdd41e718a 100644 --- a/apps/services/endorsements/api/src/app/modules/endorsement/endorsement.service.ts +++ b/apps/services/endorsements/api/src/app/modules/endorsement/endorsement.service.ts @@ -58,6 +58,50 @@ export class EndorsementService { private readonly awsService: AwsService, ) {} + async onModuleInit() { + this.logger.info( + 'Updating endorsement counts for all lists onModuleInit...', + ) + try { + await this.updateCountsForAllLists() + } catch (error) { + this.logger.error( + 'Error updating endorsement counts for all lists', + error, + ) + } + } + + async updateCountsForAllLists(): Promise { + const allLists = await this.endorsementListModel.findAll() + for (const list of allLists) { + await this.updateEndorsementCountOnList(list.id) + } + this.logger.info('All endorsement counts have been updated.') + } + + async updateEndorsementCountOnList(listId: string): Promise { + const count = await this.endorsementModel.count({ + where: { endorsementListId: listId }, + }) + const [affectedRows, updatedList] = await this.endorsementListModel.update( + { endorsementCount: count }, + { + where: { id: listId }, + returning: true, + }, + ) + if (affectedRows > 0 && updatedList[0].endorsementCount === count) { + this.logger.info( + `Successfully updated endorsement count for list "${listId}" to ${count}`, + ) + } else { + this.logger.warn( + `Failed to update endorsement count for list "${listId}". The count was not updated correctly.`, + ) + } + } + async findEndorsements({ listId }: FindEndorsementsInput, query: any) { this.logger.info(`Finding endorsements by list id "${listId}"`) @@ -111,7 +155,6 @@ export class EndorsementService { return { hasEndorsed: true } } - // FIXME: Find a way to combine with create bulk endorsements async createEndorsementOnList({ endorsementList, nationalId, @@ -136,20 +179,20 @@ export class EndorsementService { }, } - return this.endorsementModel.create(endorsement).catch((error) => { - // map meaningful sequelize errors to custom errors, else return error - switch (error.constructor) { - case UniqueConstraintError: { - this.logger.warn('Endorsement already exists in list') - throw new MethodNotAllowedException([ - 'Endorsement already exists in list', - ]) - } - default: { - throw error - } + try { + const createdEndorsement = await this.endorsementModel.create(endorsement) + await this.updateEndorsementCountOnList(endorsementList.id) + return createdEndorsement + } catch (error) { + if (error instanceof UniqueConstraintError) { + this.logger.warn('Endorsement already exists in list') + throw new MethodNotAllowedException([ + 'Endorsement already exists in list', + ]) + } else { + throw error } - }) + } } async deleteFromListByNationalId({ @@ -182,5 +225,6 @@ export class EndorsementService { ) throw new NotFoundException(["This endorsement doesn't exist"]) } + await this.updateEndorsementCountOnList(endorsementList.id) } } diff --git a/apps/services/endorsements/api/src/app/modules/endorsementList/dto/endorsementList.dto.ts b/apps/services/endorsements/api/src/app/modules/endorsementList/dto/endorsementList.dto.ts index d48a18638142..62b11bce6178 100644 --- a/apps/services/endorsements/api/src/app/modules/endorsementList/dto/endorsementList.dto.ts +++ b/apps/services/endorsements/api/src/app/modules/endorsementList/dto/endorsementList.dto.ts @@ -40,17 +40,20 @@ export class EndorsementListDto { @IsObject() meta = {} - @ApiProperty({ type: Date }) + @ApiProperty({ type: Date, default: new Date() }) // default today @Type(() => Date) @IsDate() - closedDate!: Date + openedDate!: Date - @ApiProperty({ type: Date }) + @ApiProperty({ + type: Date, + default: new Date(new Date().setMonth(new Date().getMonth() + 1)), + }) // default month from today @Type(() => Date) @IsDate() - openedDate!: Date + closedDate!: Date - @ApiProperty({ type: Boolean }) + @ApiProperty({ type: Boolean, default: false }) @IsBoolean() adminLock!: boolean } diff --git a/apps/services/endorsements/api/src/app/modules/endorsementList/endorsementList.model.ts b/apps/services/endorsements/api/src/app/modules/endorsementList/endorsementList.model.ts index b67a6c26c42d..82b4c591cea8 100644 --- a/apps/services/endorsements/api/src/app/modules/endorsementList/endorsementList.model.ts +++ b/apps/services/endorsements/api/src/app/modules/endorsementList/endorsementList.model.ts @@ -118,4 +118,15 @@ export class EndorsementList extends Model { }) @UpdatedAt readonly modified!: Date + + @ApiProperty({ + type: Number, + description: 'The number of endorsements in the list', + }) + @Column({ + type: DataType.INTEGER, + allowNull: false, + defaultValue: 0, + }) + endorsementCount!: number } diff --git a/apps/services/endorsements/api/src/app/modules/endorsementList/endorsementList.service.ts b/apps/services/endorsements/api/src/app/modules/endorsementList/endorsementList.service.ts index 36a942766817..dfddba4a6e73 100644 --- a/apps/services/endorsements/api/src/app/modules/endorsementList/endorsementList.service.ts +++ b/apps/services/endorsements/api/src/app/modules/endorsementList/endorsementList.service.ts @@ -3,10 +3,9 @@ import { Injectable, NotFoundException, BadRequestException, - ForbiddenException, } from '@nestjs/common' import { InjectModel } from '@nestjs/sequelize' -import { col, Op, Sequelize } from 'sequelize' +import { Op } from 'sequelize' import type { Logger } from '@island.is/logging' import { LOGGER_PROVIDER } from '@island.is/logging' import { EndorsementList } from './endorsementList.model' @@ -74,7 +73,6 @@ export class EndorsementListService { } } - // generic reusable query with pagination defaults async findListsGenericQuery(query: any, where: any = {}) { this.logger.info(`Finding endorsement lists`) return await paginate({ @@ -83,28 +81,8 @@ export class EndorsementListService { after: query.after, before: query.before, primaryKeyField: 'counter', - orderOption: [ - ['endorsementCounter', 'DESC'], - ['counter', 'DESC'], - ], + orderOption: [['endorsementCount', 'DESC']], where: where, - attributes: { - include: [ - [ - Sequelize.fn('COUNT', Sequelize.col('endorsements.id')), - 'endorsementCounter', - ], - ], - }, - include: [ - { - model: Endorsement, - required: false, // Required false for left outer join so that counts come for 0 as well - duplicating: false, - attributes: [], - }, - ], - group: ['EndorsementList.id'], }) } @@ -127,8 +105,6 @@ export class EndorsementListService { } async findSingleList(listId: string, user?: User, check?: boolean) { - // Check variable needed since finAll function in Endorsement controller uses this function twice - // on the second call it passes nationalID of user but does not go throught the get list pipe const isAdmin = user && check ? this.hasAdminScope(user) : false this.logger.info(`Finding single endorsement lists by id "${listId}"`) const result = await this.endorsementListModel.findOne({ @@ -259,7 +235,6 @@ export class EndorsementListService { this.logger.info(`Creating endorsement list: ${list.title}`) const endorsementList = await this.endorsementListModel.create({ ...list }) - console.log('process.env.NODE_ENV', process.env.NODE_ENV) if (process.env.NODE_ENV === 'production') { await this.emailCreated(endorsementList) } @@ -267,7 +242,6 @@ export class EndorsementListService { return endorsementList } - // generic get open lists async findOpenListsTaggedGeneralPetition(query: any) { const dateOb = new Date() try { @@ -305,9 +279,6 @@ export class EndorsementListService { } async getOwnerInfo(listId: string, owner?: string) { - // Is used by both unauthenticated users, authenticated users and admin - // Admin needs to access locked lists and can not use the EndorsementListById pipe - // Since the endpoint is not authenticated this.logger.info(`Finding single endorsement lists by id "${listId}"`) if (!owner) { const endorsementList = await this.endorsementListModel.findOne({ diff --git a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx index 3267be6545ae..904fcbad9d2a 100644 --- a/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx +++ b/apps/web/components/Organization/Wrapper/OrganizationWrapper.tsx @@ -297,7 +297,9 @@ export const OrganizationHeader: React.FC< /> ) case 'utlendingastofnun': - return ( + return n('usingDefaultHeader', false) ? ( + + ) : ( + + /** + * Should this scope be added to the actor claim object in the access token. Defaults to false. + */ + alsoForDelegatedUser?: boolean } const getScopeFields = (options: ScopeOptions): DbScope => ({ @@ -55,7 +60,7 @@ const getScopeFields = (options: ScopeOptions): DbScope => ({ description: options.description, grant_to_legal_guardians: options.delegation?.legalGuardians === true, grant_to_procuring_holders: options.delegation?.procuringHolders === true, - also_for_delegated_user: options.delegation?.custom === true, + also_for_delegated_user: options.alsoForDelegatedUser ?? false, is_access_controlled: options.accessControlled ?? false, // The scope name should be prefixed with the organisation domain, eg `@island.is/some-scope:name`. diff --git a/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts b/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts index fd07334a8a1f..2a55384d2623 100644 --- a/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/admin/delegation-admin-custom.service.ts @@ -106,7 +106,7 @@ export class DelegationAdminCustomService { const userScopes = await this.delegationResourceService.findScopes( user, - delegation.domainName, + delegation.domainName ?? null, ) await this.sequelize.transaction(async (transaction) => { diff --git a/libs/auth-api-lib/src/lib/delegations/delegations-outgoing.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations-outgoing.service.ts index 2086a3a55ee8..ab615439e5fb 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations-outgoing.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations-outgoing.service.ts @@ -11,10 +11,7 @@ import { isUuid, uuid } from 'uuidv4' import { User } from '@island.is/auth-nest-tools' import { NoContentException } from '@island.is/nest/problem' -import { - NotificationsApi, - UserSystemNotificationModule, -} from '../user-notification' +import { NotificationsApi } from '../user-notification' import { ApiScope } from '../resources/models/api-scope.model' import { DelegationScopeService } from './delegation-scope.service' @@ -345,7 +342,7 @@ export class DelegationsOutgoingService { if ( !(await this.delegationResourceService.validateScopeAccess( user, - currentDelegation.domainName, + currentDelegation.domainName ?? null, DelegationDirection.OUTGOING, [ ...(patchedDelegation.updateScopes ?? []).map((scope) => scope.name), @@ -406,7 +403,7 @@ export class DelegationsOutgoingService { const userScopes = await this.delegationResourceService.findScopes( user, - delegation.domainName, + delegation.domainName ?? null, ) await this.delegationScopeService.delete( delegationId, @@ -460,7 +457,7 @@ export class DelegationsOutgoingService { // Verify and filter scopes. const userScopes = await this.delegationResourceService.findScopeNames( user, - delegation.domainName, + delegation.domainName ?? null, direction, ) if (!userScopes.length) { diff --git a/libs/auth-api-lib/src/lib/delegations/delegations.service.ts b/libs/auth-api-lib/src/lib/delegations/delegations.service.ts index e19a0cfb370a..593cbb1b6e79 100644 --- a/libs/auth-api-lib/src/lib/delegations/delegations.service.ts +++ b/libs/auth-api-lib/src/lib/delegations/delegations.service.ts @@ -136,7 +136,7 @@ export class DelegationsService { const allowedScopes = await this.delegationResourcesService.findScopeNames( user, - delegation.domainName, + delegation.domainName ?? null, direction, ) // If the user doesn't have any allowed scope in the delegation domain we return null @@ -156,6 +156,7 @@ export class DelegationsService { * if direction is incoming is then all delegation scopes will be deleted else only user scopes * @param user User object of the authenticated user. * @param id Id of the delegation to delete + * @param direction Direction of the delegation: incoming or outgoing * @returns */ async delete( @@ -199,6 +200,7 @@ export class DelegationsService { /** * Deprecated: Use DelegationsIncomingService instead for incoming delegations. */ + /***** Incoming Delegations *****/ /** diff --git a/libs/auth-api-lib/src/lib/delegations/models/delegation.model.ts b/libs/auth-api-lib/src/lib/delegations/models/delegation.model.ts index 063ec5bbb448..fcb192dc7010 100644 --- a/libs/auth-api-lib/src/lib/delegations/models/delegation.model.ts +++ b/libs/auth-api-lib/src/lib/delegations/models/delegation.model.ts @@ -16,7 +16,6 @@ import { Table, UpdatedAt, } from 'sequelize-typescript' -import { DEFAULT_DOMAIN } from '../../types' import { DelegationDTO } from '../dto/delegation.dto' import { DelegationScope } from './delegation-scope.model' import { Domain } from '../../resources/models/domain.model' @@ -29,6 +28,12 @@ import { @Table({ tableName: 'delegation', timestamps: false, + indexes: [ + { + unique: true, + fields: ['domain_name', 'from_national_id', 'to_national_id'], + }, + ], }) export class Delegation extends Model< InferAttributes, @@ -74,11 +79,10 @@ export class Delegation extends Model< @Column({ type: DataType.STRING, - allowNull: false, - defaultValue: DEFAULT_DOMAIN, + allowNull: true, }) @ForeignKey(() => Domain) - domainName!: CreationOptional + domainName?: string /** * ReferenceId is a field for storing a reference to the zendesk ticket id diff --git a/libs/auth-api-lib/src/lib/resources/delegation-resources.service.ts b/libs/auth-api-lib/src/lib/resources/delegation-resources.service.ts index 3d55ad687b89..3f5b80d434dd 100644 --- a/libs/auth-api-lib/src/lib/resources/delegation-resources.service.ts +++ b/libs/auth-api-lib/src/lib/resources/delegation-resources.service.ts @@ -25,6 +25,7 @@ import { mapToScopeTree } from './utils/scope-tree.mapper' import type { Attributes, WhereOptions } from 'sequelize' import type { ConfigType } from '@island.is/nest/config' + type DelegationConfigType = ConfigType type ScopeRule = DelegationConfigType['customScopeRules'] extends Array< infer ScopeRule @@ -135,7 +136,7 @@ export class DelegationResourcesService { async findScopes( user: User, - domainName: string, + domainName: string | null, language?: string, direction?: DelegationDirection, ): Promise { @@ -166,7 +167,7 @@ export class DelegationResourcesService { async findScopeNames( user: User, - domainName: string, + domainName: string | null, direction?: DelegationDirection, ) { const scopes = await this.findScopesInternal({ @@ -180,7 +181,7 @@ export class DelegationResourcesService { async validateScopeAccess( user: User, - domainName: string, + domainName: string | null, direction: DelegationDirection, scopesToCheck: Array, ): Promise { @@ -236,7 +237,7 @@ export class DelegationResourcesService { attributes, }: { user: User - domainName: string + domainName: string | null language?: string direction?: DelegationDirection attributes?: Array> diff --git a/libs/portals/admin/regulations-admin/src/components/EditBasics.tsx b/libs/portals/admin/regulations-admin/src/components/EditBasics.tsx index b6f79ed5fec0..6cf202cbdd87 100644 --- a/libs/portals/admin/regulations-admin/src/components/EditBasics.tsx +++ b/libs/portals/admin/regulations-admin/src/components/EditBasics.tsx @@ -188,16 +188,30 @@ export const EditBasics = () => { setIsModalVisible(true)} - variant="text" - size="small" + message={ + - Uppfæra - + {updateText} + + + + } />