eastern-cyan
eastern-cyan•8mo 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!
9 Replies
kapa.ai
kapa.ai•8mo 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.ai•8mo ago
<@878033342845579305> kapa.ai is still learning and improving, please let us know how it did by reacting below
optimistic-gold
optimistic-gold•8mo 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.
national-gold
national-gold•8mo 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?
relaxed-coral
relaxed-coral•8mo 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.
national-gold
national-gold•8mo ago
@Batu thanks for the links and advices
subsequent-cyan
subsequent-cyan•5mo 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...
relaxed-coral
relaxed-coral•5mo 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.
molecular-blue
molecular-blue•4w ago
@Batu Thank you so much man. I appreciate your work and i now that building the entire backend for the CRM is a lot of work. I'm trying to build out the entire backend my self with the tech stack that you've suggested. I'm hope i could get some help if i get stucked at a certain point. Again thanks a lot