Skip to content

Commit

Permalink
Merge branch 'main' of github.com:island-is/island.is into j-s/civil-…
Browse files Browse the repository at this point in the history
…claimant-data-structure
  • Loading branch information
oddsson committed Sep 18, 2024
2 parents 8a21663 + e660f32 commit 2b5064c
Show file tree
Hide file tree
Showing 15 changed files with 230 additions and 78 deletions.
Original file line number Diff line number Diff line change
@@ -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')
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
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<void> {
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}"`)

Expand Down Expand Up @@ -111,7 +155,6 @@ export class EndorsementService {
return { hasEndorsed: true }
}

// FIXME: Find a way to combine with create bulk endorsements
async createEndorsementOnList({
endorsementList,
nationalId,
Expand All @@ -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({
Expand Down Expand Up @@ -182,5 +225,6 @@ export class EndorsementService {
)
throw new NotFoundException(["This endorsement doesn't exist"])
}
await this.updateEndorsementCountOnList(endorsementList.id)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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({
Expand All @@ -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'],
})
}

Expand All @@ -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({
Expand Down Expand Up @@ -259,15 +235,13 @@ 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)
}

return endorsementList
}

// generic get open lists
async findOpenListsTaggedGeneralPetition(query: any) {
const dateOb = new Date()
try {
Expand Down Expand Up @@ -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({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,9 @@ export const OrganizationHeader: React.FC<
/>
)
case 'utlendingastofnun':
return (
return n('usingDefaultHeader', false) ? (
<DefaultHeader {...defaultProps} />
) : (
<UtlendingastofnunHeader
organizationPage={organizationPage}
logoAltText={logoAltText}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
'use strict'

module.exports = {
async up(queryInterface) {
return queryInterface.sequelize.query(`
BEGIN;
-- 1. Drop the existing unique constraint on (domain_name, from_national_id, to_national_id)
ALTER TABLE delegation
DROP CONSTRAINT IF EXISTS unique_domain_from_to;
-- 2. Alter the domain_name column to allow NULL and remove the default value
ALTER TABLE delegation
ALTER COLUMN domain_name DROP DEFAULT,
ALTER COLUMN domain_name DROP NOT NULL;
-- 3. Create a new unique index to enforce uniqueness where NULL is treated as a value
CREATE UNIQUE INDEX unique_domain_from_to_index
ON delegation (COALESCE(domain_name, 'NULL'), from_national_id, to_national_id);
COMMIT;
`)
},

async down(queryInterface) {
return queryInterface.sequelize.query(`
BEGIN;
-- 1. Drop the unique index created with COALESCE
DROP INDEX IF EXISTS unique_domain_from_to_index;
-- 2. Alter the domain_name column to make it NOT NULL again and add the default value
ALTER TABLE delegation
ALTER COLUMN domain_name SET DEFAULT '@island.is',
ALTER COLUMN domain_name SET NOT NULL;
-- 3. Recreate the original unique constraint on (domain_name, from_national_id, to_national_id)
ALTER TABLE delegation ADD CONSTRAINT unique_domain_from_to
UNIQUE (domain_name, from_national_id, to_national_id);
COMMIT;
`)
},
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
module.exports = {
up(queryInterface) {
return queryInterface.sequelize.query(`
BEGIN;
UPDATE api_scope
SET also_for_delegated_user = false
WHERE name = '@island.is/signature-collection'
COMMIT;
`)
},

down(queryInterface) {
return queryInterface.sequelize.query(`
BEGIN;
UPDATE api_scope
SET also_for_delegated_user = true
WHERE name = '@island.is/signature-collection'
COMMIT;
`)
},
}
7 changes: 6 additions & 1 deletion libs/auth-api-lib/seeders/data/helpers/createScope.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,11 @@ interface ScopeOptions {
* Configures which claims the scope requires. Defaults to `nationalId`.
*/
claims?: Array<string>

/**
* Should this scope be added to the actor claim object in the access token. Defaults to false.
*/
alsoForDelegatedUser?: boolean
}

const getScopeFields = (options: ScopeOptions): DbScope => ({
Expand All @@ -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`.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down
Loading

0 comments on commit 2b5064c

Please sign in to comment.