Skip to content

Commit

Permalink
Merge pull request #1129 from givepraise/add/attestations
Browse files Browse the repository at this point in the history
Add/attestations
  • Loading branch information
kristoferlund committed Sep 4, 2023
2 parents 32cc67f + 959cf82 commit dc9e9e8
Show file tree
Hide file tree
Showing 75 changed files with 6,012 additions and 499 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- **Frontend/API**: Attestations! Communities can now create attestations based on Praise data using the Ethereum Attestation Service. Attest to top receivers, givers etc.
- **Frontend**: Custom reports can now be configured using an interactive form. #1131
- **Frontend**: New feature: Run custom reports from the reports page. #1050

Expand Down
10 changes: 10 additions & 0 deletions packages/api-types/out/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -393,7 +393,11 @@ export interface components {
/** @enum {string} */
status: 'OPEN' | 'QUANTIFY' | 'CLOSED';
/** Format: date-time */
startDate: string;
/** Format: date-time */
endDate: string;
/** @example 0x46164b8581258eec4b4f44d626925953d0d7581514d9fd1335e3bd660d48e07c */
attestationsTxHash: string;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand Down Expand Up @@ -494,7 +498,11 @@ export interface components {
/** @enum {string} */
status: 'OPEN' | 'QUANTIFY' | 'CLOSED';
/** Format: date-time */
startDate: string;
/** Format: date-time */
endDate: string;
/** @example 0x46164b8581258eec4b4f44d626925953d0d7581514d9fd1335e3bd660d48e07c */
attestationsTxHash: string;
/** Format: date-time */
createdAt: string;
/** Format: date-time */
Expand All @@ -513,6 +521,8 @@ export interface components {
UpdatePeriodInputDto: {
/** @example June 2021 */
name?: string;
/** @example 0x46164b8581258eec4b4f44d626925953d0d7581514d9fd1335e3bd660d48e07c */
attestationsTxHash?: string;
endDate?: string;
};
UserAccountWithUserRefDto: {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/openapi.json

Large diffs are not rendered by default.

11 changes: 1 addition & 10 deletions packages/api/src/auth/role-permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,16 +53,7 @@ const PERMISSIONS_ADMIN = [
Permission.PeriodAssign,
];

/**
* Root users are users that are allowed to manage the system.
* In addition to the admin permissions, they can also manage communities.
*/
const PERMISSIONS_ROOT = [
...PERMISSIONS_ADMIN,
Permission.CommunitiesCreate,
Permission.CommunitiesView,
Permission.CommunitiesUpdate,
];
const PERMISSIONS_ROOT = [...PERMISSIONS_ADMIN];

/**
* API keys can be used to access the API. They can be created with different
Expand Down
13 changes: 13 additions & 0 deletions packages/api/src/community/dto/find-all-communities-query.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsOptional } from 'class-validator';
import { PaginatedQueryDto } from '../../shared/dto/pagination-query.dto';

export class FindAllCommunitiesQueryDto extends PaginatedQueryDto {
@ApiProperty({
required: false,
type: 'string',
example: 'hostname.givepraise.xyz',
})
@IsOptional()
hostname?: string;
}
14 changes: 11 additions & 3 deletions packages/api/src/periods/dto/update-period-input.dto.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,12 @@
import { PartialType } from '@nestjs/swagger';
import { CreatePeriodInputDto } from './create-period-input.dto';
import { ApiProperty, PartialType, PickType } from '@nestjs/swagger';
import { Period } from '../schemas/periods.schema';
import { IsDateString, IsOptional } from 'class-validator';

export class UpdatePeriodInputDto extends PartialType(CreatePeriodInputDto) {}
export class UpdatePeriodInputDto extends PartialType(
PickType(Period, ['name', 'attestationsTxHash'] as const),
) {
@ApiProperty({ type: 'string', required: false })
@IsDateString()
@IsOptional()
endDate?: string;
}
12 changes: 12 additions & 0 deletions packages/api/src/periods/schemas/periods.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,25 @@ export class Period {
})
status: PeriodStatusType;

@ApiResponseProperty()
startDate?: Date;

@ApiResponseProperty()
@Prop({
required: true,
type: Date,
})
endDate: Date;

@ApiProperty({
example:
'0x46164b8581258eec4b4f44d626925953d0d7581514d9fd1335e3bd660d48e07c',
type: 'string',
})
@IsString()
@Prop({ required: false })
attestationsTxHash?: string;

@Prop({ type: Date })
@ApiResponseProperty()
createdAt: Date;
Expand Down
30 changes: 27 additions & 3 deletions packages/api/src/periods/services/periods.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ export class PeriodsService {
options: PaginatedQueryDto,
): Promise<PeriodPaginatedResponseDto> {
const {
sortColumn = 'createdAt',
sortColumn = 'endDate',
sortType = 'desc',
page = 1,
limit = 100,
Expand All @@ -77,11 +77,26 @@ export class PeriodsService {
page,
limit,
sort: sortColumn && sortType ? { [sortColumn]: sortType } : undefined,
lean: true,
});

if (!periodPagination)
throw new ApiException(errorMessages.FAILED_TO_PAGINATE_PERIOD_DATA);

const periods = periodPagination.docs;

// Sort the periods by endDate in ascending order
periods.sort((a, b) => a.endDate.getTime() - b.endDate.getTime());

// Iterate through the periods and set the startDate based on the previous period's endDate
for (let i = 1; i < periods.length; i++) {
periods[i].startDate = periods[i - 1].endDate;
}

// Set the startDate for the first period, if needed
if (periods.length > 0 && !periods[0].startDate) {
periods[0].startDate = new Date('2000-01-01T00:00:00.000Z');
}
return periodPagination;
}

Expand Down Expand Up @@ -168,9 +183,9 @@ export class PeriodsService {
const period = await this.periodModel.findById(_id);
if (!period) throw new ApiException(errorMessages.PERIOD_NOT_FOUND);

const { name, endDate } = data;
const { name, endDate, attestationsTxHash } = data;

if (!name && !endDate)
if (!name && !endDate && !attestationsTxHash)
throw new ApiException(
errorMessages.UPDATE_PERIOD_NAME_OR_END_DATE_MUST_BE_SPECIFIED,
);
Expand Down Expand Up @@ -206,6 +221,14 @@ export class PeriodsService {
period.endDate = newEndDate;
}

if (attestationsTxHash) {
eventLogMessages.push(
`Updated the transaction hash of period "${period.name}" to "${attestationsTxHash}"`,
);

period.attestationsTxHash = attestationsTxHash;
}

await period.save();

logger.info(eventLogMessages.join(', '));
Expand Down Expand Up @@ -414,6 +437,7 @@ export class PeriodsService {

const periodDetails = {
...period,
startDate: previousPeriodEndDate,
numberOfPraise,
receivers,
givers,
Expand Down
4 changes: 2 additions & 2 deletions packages/api/src/shared/exceptions/error-messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,8 @@ export const errorMessages: { [key: string]: ErrorMessage } = {
httpStatusCode: 500,
code: 1013,
},
UPDATE_PERIOD_NAME_OR_END_DATE_MUST_BE_SPECIFIED: {
message: 'Updated name or endDate to must be specified',
UPDATE_PERIOD_SOME_PROP_MUST_BE_SPECIFIED: {
message: 'At least one property must be specified',
httpStatusCode: 400,
code: 1013,
},
Expand Down
1 change: 0 additions & 1 deletion packages/discord-bot/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,6 @@ discordClient.on('interactionCreate', async (interaction): Promise<void> => {
discordClient,
interaction.guild.id
);
console.log(community);

if (community) {
await command.execute(discordClient, interaction, community.hostname);
Expand Down
4 changes: 2 additions & 2 deletions packages/discord-bot/src/utils/api-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,8 @@ export type PraiseForwardInputDto =

export type Setting = components['schemas']['Setting'];

export type CommunityPaginatedResponseDto =
components['schemas']['CommunityPaginatedResponseDto'];
export type CommunityFindAllResponseDto =
components['schemas']['CommunityFindAllResponseDto'];
export type Community = components['schemas']['Community'];

export type PeriodPaginatedResponseDto =
Expand Down
9 changes: 2 additions & 7 deletions packages/discord-bot/src/utils/embeds/praiseSuccessEmbed.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,6 @@ export const praiseSuccessEmbed = async (
.replace('{@receivers}', `${receivers.join(', ')}`)
.replace('{reason}', reason);

try {
const embed = new EmbedBuilder().setColor(0xe6007e).setDescription(msg);
return embed;
} catch (error) {
console.log(error);
throw error;
}
const embed = new EmbedBuilder().setColor(0xe6007e).setDescription(msg);
return embed;
};
4 changes: 2 additions & 2 deletions packages/discord-bot/src/utils/getHost.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { apiGet } from './api';
import Keyv from 'keyv';
import { Community, CommunityPaginatedResponseDto } from './api-schema';
import { Community, CommunityFindAllResponseDto } from './api-schema';
import { DiscordClient } from '../interfaces/DiscordClient';

/**
Expand All @@ -11,7 +11,7 @@ export const buildCommunityCache = async (cache: Keyv): Promise<void> => {
let currPage = 1;
let totalPages = 1;
while (currPage <= totalPages) {
const communityList = await apiGet<CommunityPaginatedResponseDto>(
const communityList = await apiGet<CommunityFindAllResponseDto>(
`/communities?page=${currPage}`
).then((res) => res.data);

Expand Down
16 changes: 6 additions & 10 deletions packages/discord-bot/src/utils/getUserAccount.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,15 +67,11 @@ export const getUserAccount = async (
.then((res) => res.data.filter((acc) => acc.platform === 'DISCORD'))
.catch(() => undefined);

try {
if (!data || !data.length) {
return await createUserAccount(user, host);
} else {
return await updateUserAccount(data[0], user, host);
}
} catch (e) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
console.log((e as any).data);
throw e;
if (!data || !data.length) {
return await createUserAccount(user, host);
} else {
return await updateUserAccount(data[0], user, host);
}
// No console.log please
// console.log((e as any).data);
};
1 change: 0 additions & 1 deletion packages/discord-bot/src/utils/givePraise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,6 @@ export const givePraise = async (

await Promise.all(
praiseItems.map(async (praise) => {
console.log(praiseItems, receivers);
await sendReceiverDM(
praise._id,
receivers.filter(
Expand Down
8 changes: 5 additions & 3 deletions packages/frontend/craco.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ module.exports = {
* Possible alternatives: react-app-rewired or ejecting from CRA
*/
fallback: {
'util': require.resolve("util/"),
util: require.resolve('util/'),
stream: require.resolve('stream-browserify'),
assert: require.resolve('assert/'),
},
},
// with CRA 5 (webpack5), sourceMapLoader now complains about every third-party app that was compiled from
// typescript but doesn't have 'ts' files. This line ignores them.
// See: https://github.com/facebook/create-react-app/issues/11924
ignoreWarnings: [/to parse source map/i],
}
}
},
},
};
12 changes: 10 additions & 2 deletions packages/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,15 @@
"description": "The Praise dashboard built on React/Recoil/Tailwind CSS.",
"private": true,
"engines": {
"node": ">=10.0.0"
"node": ">=16.0.0"
},
"dependencies": {
"@craco/craco": "^6.4.3",
"@duckdb/duckdb-wasm": "^1.20.0",
"@emotion/react": "^11.7.1",
"@emotion/styled": "^11.6.0",
"@endo/static-module-record": "^0.7.16",
"@ethereum-attestation-service/eas-sdk": "^1.1.0-beta.3",
"@ethersproject/providers": "^5.5.3",
"@fortawesome/fontawesome-svg-core": "^6.1.1",
"@fortawesome/free-brands-svg-icons": "^6.1.1",
Expand All @@ -23,19 +24,25 @@
"@mui/material": "^5.3.0",
"@observablehq/plot": "^0.6.5",
"@rainbow-me/rainbowkit": "^1.0.7",
"@safe-global/api-kit": "^1.3.0",
"@safe-global/protocol-kit": "^1.2.0",
"@safe-global/safe-core-sdk-types": "^2.2.0",
"@sgratzl/chartjs-chart-boxplot": "^4.1.1",
"@tailwindcss/container-queries": "^0.1.1",
"@tailwindcss/forms": "latest",
"@types/react-csv": "^1.1.2",
"@typescript-eslint/eslint-plugin": "^5.18.0",
"@typescript-eslint/parser": "^5.18.0",
"@ukstv/jazzicon-react": "^1.0.0",
"@wagmi/core": "~1.3.9",
"api-types": "0.1.0",
"assert": "^2.0.0",
"autoprefixer": "^10.4.7",
"axios": "^0.22.0",
"chart.js": "^4.2.0",
"chartjs-chart-treemap": "^2.3.0",
"craco-alias": "^3.0.1",
"csv-parse": "^5.4.0",
"date-fns": "^2.25.0",
"date-fns-tz": "^1.3.3",
"discord-markdown": "^2.5.1",
Expand All @@ -62,11 +69,12 @@
"recoil-persist": "^4.2.0",
"ses": "^0.18.7",
"source-map-explorer": "^2.5.2",
"stream-browserify": "^3.0.0",
"tailwindcss": "^3.0.24",
"use-error-boundary": "^2.0.6",
"util": "^0.12.4",
"viem": "^0.3.33",
"wagmi": "^1.0.5",
"wagmi": "~1.3.9",
"wasm-check": "^2.1.2"
},
"scripts": {
Expand Down
Binary file added packages/frontend/public/eas-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
8 changes: 6 additions & 2 deletions packages/frontend/src/components/auth/SignMessageButton.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { useSignMessage } from 'wagmi';
import { LoaderSpinner } from '@/components/ui/LoaderSpinner';
import { Button } from '../ui/Button';
import { faPrayingHands } from '@fortawesome/free-solid-svg-icons';
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome';

interface Props {
text: string;
Expand All @@ -26,7 +27,10 @@ const SignMessageButton = ({
});

return isLoading || isSuccess ? (
<LoaderSpinner />
<Button disabled>
<FontAwesomeIcon icon={faPrayingHands} spin className="w-4 mr-2" />
Signing in...
</Button>
) : (
<Button onClick={(): void => signMessage()}>{text}</Button>
);
Expand Down
Loading

0 comments on commit dc9e9e8

Please sign in to comment.