Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Current community #1135

Merged
merged 10 commits into from
Aug 18, 2023
25 changes: 23 additions & 2 deletions packages/api-types/out/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -215,6 +215,9 @@ export interface paths {
'/api/communities/isNameAvailable': {
get: operations['CommunityController_isNameAvailable'];
};
'/api/communities/current': {
get: operations['CommunityController_current'];
};
'/api/communities/{id}/discord/link': {
/** Link discord to community */
patch: operations['CommunityController_linkDiscord'];
Expand Down Expand Up @@ -987,6 +990,12 @@ export interface components {
isPublic: boolean;
/** @enum {string} */
discordLinkState: 'NOT_SET' | 'PENDING' | 'ACTIVE' | 'DEACTIVE';
/**
* @example {
* "attestations": true
* }
*/
features: Record<string, never>;
};
UpdateCommunityInputDto: {
/** @example banklessdao.givepraise.xyz */
Expand All @@ -1003,7 +1012,7 @@ export interface components {
*/
owners?: string[];
};
CommunityPaginatedResponseDto: {
CommunityFindAllResponseDto: {
/** @example 1200 */
totalDocs: number;
/** @example 10 */
Expand Down Expand Up @@ -2113,13 +2122,15 @@ export interface operations {
page: number;
sortColumn?: string;
sortType?: 'asc' | 'desc';
/** @example hostname.givepraise.xyz */
hostname?: string;
};
};
responses: {
/** @description All communities */
200: {
content: {
'application/json': components['schemas']['CommunityPaginatedResponseDto'];
'application/json': components['schemas']['CommunityFindAllResponseDto'];
};
};
};
Expand Down Expand Up @@ -2192,6 +2203,16 @@ export interface operations {
};
};
};
CommunityController_current: {
responses: {
/** @description Returns the current community, based on hostname */
200: {
content: {
'application/json': components['schemas']['Community'];
};
};
};
};
/** Link discord to community */
CommunityController_linkDiscord: {
requestBody: {
Expand Down
2 changes: 1 addition & 1 deletion packages/api/openapi.json

Large diffs are not rendered by default.

25 changes: 20 additions & 5 deletions packages/api/src/community/community.controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import {
Patch,
Post,
Query,
Req,
SerializeOptions,
UseInterceptors,
} from '@nestjs/common';
Expand All @@ -15,8 +16,7 @@ import { Permission } from '../auth/enums/permission.enum';
import { Community } from './schemas/community.schema';
import { MongooseClassSerializerInterceptor } from '../shared/interceptors/mongoose-class-serializer.interceptor';
import { ObjectIdPipe } from '../shared/pipes/object-id.pipe';
import { CommunityPaginatedResponseDto } from './dto/community-pagination-model.dto';
import { PaginatedQueryDto } from '../shared/dto/pagination-query.dto';
import { CommunityFindAllResponseDto } from './dto/find-all-response.dto';
import { ObjectId, Types } from 'mongoose';
import { Permissions } from '../auth/decorators/permissions.decorator';
import { CreateCommunityInputDto } from './dto/create-community-input.dto';
Expand All @@ -29,6 +29,9 @@ import { IsNameAvailableResponseDto } from './dto/is-name-available-response-dto
import { IsNameAvailableRequestDto } from './dto/is-name-available-request-dto';
import { EventLogService } from '../event-log/event-log.service';
import { EventLogTypeKey } from '../event-log/enums/event-log-type-key';
import { CommunityFindAllQueryDto } from './dto/find-all-query.dto';
import { Public } from '../shared/decorators/public.decorator';
import { Request } from 'express';

@Controller('communities')
@ApiTags('Communities')
Expand Down Expand Up @@ -78,12 +81,12 @@ export class CommunityController {
@ApiResponse({
status: 200,
description: 'All communities',
type: CommunityPaginatedResponseDto,
type: CommunityFindAllResponseDto,
})
@UseInterceptors(MongooseClassSerializerInterceptor(Community))
async findAll(
@Query() options: PaginatedQueryDto,
): Promise<CommunityPaginatedResponseDto> {
@Query() options: CommunityFindAllQueryDto,
): Promise<CommunityFindAllResponseDto> {
return this.communityService.findAllPaginated(options);
}

Expand All @@ -99,6 +102,18 @@ export class CommunityController {
return await this.communityService.isCommunityNameAvailable(options.name);
}

@Get('/current')
@Public()
@ApiResponse({
status: 200,
description: 'Returns the current community, based on hostname',
type: Community,
})
@UseInterceptors(MongooseClassSerializerInterceptor(Community))
async current(@Req() req: Request): Promise<Community> {
return this.communityService.findOne({ host: req.hostname });
}

@Get(':id')
@Permissions(Permission.CommunitiesView)
@ApiResponse({
Expand Down
11 changes: 6 additions & 5 deletions packages/api/src/community/community.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,7 @@ import { InjectConnection, InjectModel } from '@nestjs/mongoose';
import { Connection, Types } from 'mongoose';
import { ApiException } from '../shared/exceptions/api-exception';
import { Community } from './schemas/community.schema';
import { PaginatedQueryDto } from '../shared/dto/pagination-query.dto';
import { CommunityPaginatedResponseDto } from './dto/community-pagination-model.dto';
import { CommunityFindAllResponseDto } from './dto/find-all-response.dto';
import { CreateCommunityInputDto } from './dto/create-community-input.dto';
import { UpdateCommunityInputDto } from './dto/update-community-input.dto';
import { LinkDiscordBotDto } from './dto/link-discord-bot.dto';
Expand All @@ -20,6 +19,7 @@ import { databaseExists } from '../database/utils/database-exists';
import { hostNameToDbName } from '../database/utils/host-name-to-db-name';
import { PaginateModel } from '../shared/interfaces/paginate-model.interface';
import { IsNameAvailableResponseDto } from './dto/is-name-available-response-dto';
import { CommunityFindAllQueryDto } from './dto/find-all-query.dto';

@Injectable()
export class CommunityService {
Expand Down Expand Up @@ -56,10 +56,11 @@ export class CommunityService {
* @returns
*/
async findAllPaginated(
options: PaginatedQueryDto,
): Promise<CommunityPaginatedResponseDto> {
options: CommunityFindAllQueryDto,
): Promise<CommunityFindAllResponseDto> {
const { sortColumn, sortType } = options;
const query = {} as any;
const { hostname } = options;
const query = options.hostname ? { hostname } : {};

// Sorting - defaults to descending
const sort =
Expand Down
13 changes: 13 additions & 0 deletions packages/api/src/community/dto/find-all-query.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { ApiProperty } from '@nestjs/swagger';
import { PaginatedQueryDto } from '../../shared/dto/pagination-query.dto';
import { IsOptional } from 'class-validator';

export class CommunityFindAllQueryDto extends PaginatedQueryDto {
@ApiProperty({
required: false,
type: 'string',
example: 'hostname.givepraise.xyz',
})
@IsOptional()
hostname?: string;
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { Type } from 'class-transformer';
import { PaginatedResponseDto } from '../../shared/dto/paginated-response.dto';
import { Community } from '../schemas/community.schema';

export class CommunityPaginatedResponseDto extends PaginatedResponseDto {
export class CommunityFindAllResponseDto extends PaginatedResponseDto {
@ApiResponseProperty({
type: [Community],
})
Expand Down
16 changes: 16 additions & 0 deletions packages/api/src/community/schemas/community.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import { isValidCommunityName } from '../utils/is-valid-community-name';
import { isValidOwners } from '../utils/is-valid-owners';
import { isValidHostname } from '../utils/is-valid-hostname';
import mongoosePaginate from 'mongoose-paginate-v2';
import { Features } from '../types/features.type';

export type CommunityDocument = Community & Document;

Expand Down Expand Up @@ -144,6 +145,21 @@ export class Community {
required: true,
})
discordLinkState: string;

@ApiProperty({
example: {
attestations: true,
},
})
@IsOptional()
@Prop({
type: Object,
required: false,
default: {
attestations: false,
},
})
features?: Features;
}

export const CommunitySchema = SchemaFactory.createForClass(Community);
Expand Down
6 changes: 6 additions & 0 deletions packages/api/src/community/types/features.type.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
/**
* The features object is used to enable or disable features for a community.
*/
export type Features = {
attestations: boolean;
};
26 changes: 26 additions & 0 deletions packages/frontend/src/model/community/community.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { AxiosError, AxiosResponse } from 'axios';
import { selector } from 'recoil';
import { ApiGet, isResponseOk } from '../api';
import { Community } from './dto/community.dto';

export const CurrentCommunityQuery = selector({
key: 'CurrentCommunity',
get: ({ get }): AxiosResponse<Community> | AxiosError => {
return get(ApiGet({ url: '/communities/current' })) as
| AxiosResponse<Community>
| AxiosError;
},
});

export const CurrentCommunity = selector({
key: 'AllReports',
get: ({ get }): Community | undefined => {
const response = get(CurrentCommunityQuery);

if (isResponseOk(response)) {
return response.data;
}

return;
},
});
3 changes: 3 additions & 0 deletions packages/frontend/src/model/community/dto/community.dto.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import { components } from 'api-types';

export type Community = components['schemas']['Community'];
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import { Box } from '@/components/ui/Box';
import { SettingsSubgroup } from './SettingsSubgroup';
import ApplicationSettingsApiKeys from './ApplicationSettingsApiKeys';
import { Setting as SettingDto } from '@/model/settings/dto/setting.dto';
import { useRecoilValue } from 'recoil';
import { CurrentCommunity } from '../../../model/community/community';
import { Jazzicon } from '@ukstv/jazzicon-react';

interface Params {
settings: SettingDto[] | undefined;
Expand All @@ -16,13 +19,55 @@ export const ApplicationSettings = ({
settings,
parentOnSubmit,
}: Params): JSX.Element | null => {
const community = useRecoilValue(CurrentCommunity);
if (!settings) return null;

return (
<>
<Box className="mb-6">
<SettingsSubgroup header="Application Settings">
<SettingsForm settings={settings} parentOnSubmit={parentOnSubmit} />
<>
<div className="flex flex-col gap-4 mb-4">
<div>
<label className="block font-bold group">Creator address</label>
<div className="mb-2 text-sm text-warm-gray-400">
The creator address is the address that represents this
community onchain. To be able to create attestations or
distribute tokens from this address, it should be a{' '}
<a
href="https://safe.global"
target="_blank"
rel="noreferrer"
>
Safe
</a>{' '}
address.
</div>
<div>
<Jazzicon
address={community?.creator || ''}
className="inline-block w-4"
/>{' '}
{community?.creator}
</div>
</div>
<div>
<label className="block font-bold group">Owner addresses</label>
<div className="mb-2 text-sm text-warm-gray-400">
The owner addresses represent the identities that will allways
have adin permissions in this Praise community.
</div>
<div>
{community?.owners.map((owner) => (
<div key={owner}>
<Jazzicon address={owner} className="inline-block w-4" />{' '}
{owner}
</div>
))}
</div>
</div>
</div>
<SettingsForm settings={settings} parentOnSubmit={parentOnSubmit} />
</>
</SettingsSubgroup>
</Box>
<Box className="mb-6">
Expand Down