secure-lavender
secure-lavender7mo ago

source code for the backend of the CRM example?

Hi friends, im a student in uni and i am very intrigued by refine's take on UI framework. I am currently playing around with the CRM app in the example and find the UI fascinating -- is the backend for this app public anywhere? I'd love to study it and how this stack works. Lmk if this is possible!
8 Replies
kapa.ai
kapa.ai7mo ago
Hi @bryan_3mavericks. I'm kapa.ai, your friendly support bot. I will try to answer your question by searching through the knowledge base. :wave: ---------------------- Hello! I'm glad to hear that you're interested in refine and its CRM example app. The source code for the CRM example app is indeed public and can be found on GitHub. Here is the link to the source code (https://github.com/refinedev/refine/tree/master/examples/app-crm). However, please note that this is the frontend code of the application. The backend code isn't specified in the provided extracts. Refine is a frontend framework and it can be integrated with various backend services. In the case of the CRM app, it's built using refine, Ant Design, and GraphQL, but the specific backend service isn't mentioned. If you're interested in how refine can be integrated with different backend services, refine has connectors for 15+ backend services, including REST API, GraphQL, and popular services like Airtable, Strapi, Supabase, Firebase, and Directus. You can find more information about this in the refine documentation (https://refine.dev/docs/). I hope this helps! If you have any more questions, feel free to ask. ---------------------- Note: you can ask me a follow up question by @ mentioning me again :speech_balloon: ----------------------
kapa.ai
kapa.ai7mo ago
<@878033342845579305> kapa.ai is still learning and improving, please let us know how it did by reacting below
eager-peach
eager-peach7mo ago
Hey @bryan_3mavericks, thank you for your interest in Refine, hope you're having fun playing around with it 😅 Currently we've not published our backend for the CRM app anywhere but I think @batuhanw can give you couple of implementation tips to go with it. We're using Nest.js + Nest.js Query with very little customization on top.
xenogeneic-maroon
xenogeneic-maroon7mo ago
Hi aliemir, I have the same need but with the difference that I would like to train myself switching the current dataprovider from graphql to Rest API, and then Supabase. Any advices on how this could be address?
robust-apricot
robust-apricot7mo ago
Hello folks, you can check https://api.crm.refine.dev/graphql to explore schema. It's public. We are using Nest.js Query package for CRM API Backend: https://doug-martin.github.io/nestjs-query/ Check here on backend configuration details: https://refine.dev/docs/data/packages/nestjs-query/#backend-configuration
Nestjs-query | Nestjs-query
Easy CRUD for GraphQL.
Nest.js Query | refine
Refine provides a data provider for APIs powered with Nest.js Query, a module for Nest.js that provides easier ways to build CRUD graphql APIs.
xenogeneic-maroon
xenogeneic-maroon7mo ago
@Batu thanks for the links and advices
harsh-harlequin
harsh-harlequin3mo ago
@Batu I'm trying to understand where is all the data in CRM-App>Settings>AuditLog generated from, because that repo does not have auditLogProvider configured and I see nothing sent over the network. Is this all handled on the server side? I thought I could see the server code and landed into this conversation where you say that the code is not public. Could you confirm that I am understanding this correctly? And I believe the API server should be the one to generate audit events, not the UI, so I would probably do it at the server side anyway...
robust-apricot
robust-apricot3mo ago
Hey @kurush you are right! Audit logs are generated on the server side. We are using typeorm's subscribers to generate those logs for us. Here is an example subscriber (All other are %95 same).
import {
DataSource,
EntitySubscriberInterface,
EventSubscriber,
InsertEvent,
Repository,
UpdateEvent,
} from 'typeorm';
import { ContactEntity } from '../entities/contact.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { AuditEntity } from '../entities/audit.entity';
import { AuditAction } from '../enums';
import { InjectPubSub } from '@ptc-org/nestjs-query-graphql';
import { PubSub } from 'graphql-subscriptions';
import { faker } from '@faker-js/faker';

@EventSubscriber()
export class ContactSubscriber implements EntitySubscriberInterface<ContactEntity> {
constructor(
dataSource: DataSource,
@InjectRepository(AuditEntity) private auditRepo: Repository<AuditEntity>,
@InjectPubSub() readonly pubSub?: PubSub,
) {
if (pubSub) {
dataSource.subscribers.push(this);
}
}

listenTo() {
return ContactEntity;
}

async afterInsert(event: InsertEvent<ContactEntity>): Promise<void> {
const changes = Object.keys(event.entity)
.filter((k) => !['createdAt', 'updatedAt'].includes(k))
.map((key) => {
if (typeof event.entity[key] === 'object') {
return {
field: key,
from: null,
to: event.entity[key]?.id,
};
}
return { field: key, from: null, to: event.entity[key] };
});

const record = await this.auditRepo.save({
action: AuditAction.CREATE,
targetEntity: ContactEntity.name.replace('Entity', ''),
targetId: event.entity.id,
changes,
user: event.entity.createdBy,
});

await this.pubSub?.publish('createdAudit-{}', { createdAudit: record });
}

async afterUpdate(event: UpdateEvent<ContactEntity>): Promise<any> {
if (!event?.entity?.id) return;

const changes = event.updatedColumns.map((column) => ({
field: column.propertyName,
from: event.databaseEntity[column.propertyName],
to: event.entity[column.propertyName],
}));

const pastDate = this.pubSub ? new Date() : faker.date.recent({ days: 7 });

const record = await this.auditRepo.save({
action: AuditAction.UPDATE,
targetEntity: ContactEntity.name.replace('Entity', ''),
targetId: event.entity.id,
changes,
before: event.databaseEntity,
after: event.entity,
user: event.entity.updatedBy,
createdAt: pastDate,
updatedAt: pastDate,
});

await this.pubSub?.publish('createdAudit-{}', { createdAudit: record });
}
}
import {
DataSource,
EntitySubscriberInterface,
EventSubscriber,
InsertEvent,
Repository,
UpdateEvent,
} from 'typeorm';
import { ContactEntity } from '../entities/contact.entity';
import { InjectRepository } from '@nestjs/typeorm';
import { AuditEntity } from '../entities/audit.entity';
import { AuditAction } from '../enums';
import { InjectPubSub } from '@ptc-org/nestjs-query-graphql';
import { PubSub } from 'graphql-subscriptions';
import { faker } from '@faker-js/faker';

@EventSubscriber()
export class ContactSubscriber implements EntitySubscriberInterface<ContactEntity> {
constructor(
dataSource: DataSource,
@InjectRepository(AuditEntity) private auditRepo: Repository<AuditEntity>,
@InjectPubSub() readonly pubSub?: PubSub,
) {
if (pubSub) {
dataSource.subscribers.push(this);
}
}

listenTo() {
return ContactEntity;
}

async afterInsert(event: InsertEvent<ContactEntity>): Promise<void> {
const changes = Object.keys(event.entity)
.filter((k) => !['createdAt', 'updatedAt'].includes(k))
.map((key) => {
if (typeof event.entity[key] === 'object') {
return {
field: key,
from: null,
to: event.entity[key]?.id,
};
}
return { field: key, from: null, to: event.entity[key] };
});

const record = await this.auditRepo.save({
action: AuditAction.CREATE,
targetEntity: ContactEntity.name.replace('Entity', ''),
targetId: event.entity.id,
changes,
user: event.entity.createdBy,
});

await this.pubSub?.publish('createdAudit-{}', { createdAudit: record });
}

async afterUpdate(event: UpdateEvent<ContactEntity>): Promise<any> {
if (!event?.entity?.id) return;

const changes = event.updatedColumns.map((column) => ({
field: column.propertyName,
from: event.databaseEntity[column.propertyName],
to: event.entity[column.propertyName],
}));

const pastDate = this.pubSub ? new Date() : faker.date.recent({ days: 7 });

const record = await this.auditRepo.save({
action: AuditAction.UPDATE,
targetEntity: ContactEntity.name.replace('Entity', ''),
targetId: event.entity.id,
changes,
before: event.databaseEntity,
after: event.entity,
user: event.entity.updatedBy,
createdAt: pastDate,
updatedAt: pastDate,
});

await this.pubSub?.publish('createdAudit-{}', { createdAudit: record });
}
}
https://doug-martin.github.io/nestjs-query/docs/graphql/subscriptions Make sure to pass enableSubscriptions: true in your resolver config:
{
pagingStrategy: PagingStrategies.OFFSET,
enableTotalCount: true,
EntityClass: ContactEntity,
DTOClass: ContactModel,
CreateDTOClass: ContactCreateInput,
UpdateDTOClass: ContactUpdateInput,
guards: [AuthGuard],
enableSubscriptions: true,
},
{
pagingStrategy: PagingStrategies.OFFSET,
enableTotalCount: true,
EntityClass: ContactEntity,
DTOClass: ContactModel,
CreateDTOClass: ContactCreateInput,
UpdateDTOClass: ContactUpdateInput,
guards: [AuthGuard],
enableSubscriptions: true,
},
Subscriptions | Nestjs-query
Before reading this it is recommended to read the nestjs graphql subscriptions docs.