Skip to content

Commit

Permalink
initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
SteveBunlon committed Sep 14, 2023
0 parents commit a141c91
Show file tree
Hide file tree
Showing 8 changed files with 3,095 additions and 0 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.idea
18 changes: 18 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
FROM node:18-alpine

WORKDIR /usr/src/app

COPY package*.json ./

RUN npm install
# If you are building your code for production
# RUN npm ci --only=production

# Bundle app source
COPY . .

RUN npm run build

EXPOSE ${APPLICATION_PORT}

CMD ["npm", "start"]
16 changes: 16 additions & 0 deletions docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
version: '3.4'
services:
app:
build:
context: .
container_name: demo_real_estate
env_file:
- .env
environment:
- DATABASE_URL=${DOCKER_DATABASE_URL}
ports:
- "${APPLICATION_PORT}:${APPLICATION_PORT}"
volumes:
- ./:/usr/src/app
- /usr/src/app/dist
- /usr/src/app/node_modules
211 changes: 211 additions & 0 deletions index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
// npm run start:watch
import type { SslMode } from '@forestadmin/datasource-sql';
import type { Schema } from './typings';

import 'dotenv/config';
import { createAgent } from '@forestadmin/agent';
import { createSqlDataSource } from '@forestadmin/datasource-sql';

// This object allows to configure your Forest Admin panel
const agent = createAgent<Schema>({
// Security tokens
authSecret: process.env.FOREST_AUTH_SECRET!,
envSecret: process.env.FOREST_ENV_SECRET!,

// Make sure to set NODE_ENV to 'production' when you deploy your project
isProduction: process.env.NODE_ENV === 'production',

// Autocompletion of collection names and fields
typingsPath: './typings.ts',
typingsMaxDepth: 5,
});

// Connect your datasources
// All options are documented at https://docs.forestadmin.com/developer-guide-agents-nodejs/data-sources/connection
agent.addDataSource(
createSqlDataSource({
uri: process.env.DATABASE_URL,
schema: process.env.DATABASE_SCHEMA,
sslMode: process.env.DATABASE_SSL_MODE as SslMode,
}),
);

// SMART ACTIONS
// Deal
// TODO: add constraint stage has to be "new"
agent.customizeCollection('deal', collection => {
collection.addAction('Close deal and create contract', {
scope: 'Single',
form: [
{
label: 'commission_rate',
description: 'Commission rate',
type: 'Number',
isRequired: true,
},
{
label: 'start_at',
description: 'Start date of the contract',
type: 'Date',
isRequired: true,
},
],
execute: async (context, resultBuilder) => {
// Retrieve values entered in the form and columns from the selected record.
const { commission_rate, start_at } = context.formValues;
const { id, owner } = await context.getRecord([
'id',
'owner'
]);

// TODO: create the contract
context.dataSource.getCollection('contract').create([{
'deal_id': id,
'commission_rate': commission_rate,
'start_at': start_at
}]);

// Close deal
const now = new Date();
await context.collection.update(context.filter, { stage: 'closed', closed_at: now.toISOString() });

// Success
return resultBuilder.success('Deal closed and contract created !');
},
});
});

// TODO: add constraint stage has to be "new"
agent.customizeCollection('deal', collection =>
collection.addAction('Close deal', {
scope: 'Bulk',
execute: async (context, resultBuilder) => {
const now = new Date();
await context.collection.update(context.filter, { stage: 'closed', closed_at: now.toISOString() });
return resultBuilder.success('Deal closed !');
},
}),
);

// TODO: add constraint stage has to be "new"
agent.customizeCollection('deal', collection =>
collection.addAction('Mark as rejected', {
scope: 'Bulk',
execute: async (context, resultBuilder) => {
await context.collection.update(context.filter, { stage: 'rejected' });
return resultBuilder.success('Deal rejected: please add a note to explain why');
},
}),
);

// TODO: to review by Steve > how to create a default "new" value for the read-only "stage" column
agent.customizeCollection('deal', collection =>
collection.replaceFieldWriting('stage', async (value, context) => {
switch (context.action) {
case 'create':
return { stage: 'new' };
default:
throw new Error('Unexpected value');
}
})
);

// Customer
agent.customizeCollection('customer', collection =>
collection.addAction('Mark as customer', {
scope: 'Single',
execute: async context => {
await context.collection.update(context.filter, { customer_type: 'customer' });
},
}),
);

agent.customizeCollection('customer', collection =>
collection.addAction('Mark as ex-customer', {
scope: 'Single',
execute: async context => {
await context.collection.update(context.filter, { customer_type: 'ex-customer' });
},
}),
);

// SMART FIELDS
// Housing
agent.customizeCollection('housing', collection => {
collection.addField('displayName', {
// Type of the new field
columnType: 'String',

// Dependencies which are needed to compute the new field (must not be empty)
dependencies: ['num_rooms', 'monthly_rent', 'city'],

// Compute function for the new field
getValues: (records, context) =>
records.map(r => `${r.num_rooms} rooms for \$${r.monthly_rent} in ${r.city}`),
});
});

// Contract
agent.customizeCollection('contract', collection => {
collection.addField('displayName', {
// Type of the new field
columnType: 'String',

// Dependencies which are needed to compute the new field (must not be empty)
dependencies: ['deal:housing:num_rooms', 'deal:housing:monthly_rent', 'deal:housing:city'],

// Compute function for the new field
getValues: (records, context) =>
records.map(r => `${r.deal.housing.num_rooms} room(s) for \$${r.deal.housing.monthly_rent} in ${r.deal.housing.city}`),
});
});


// Add customizations here.
// For instance, you can code custom actions, charts, create new fields or relationships, load plugins.
// As your project grows, you will need to split it into multiple files!
//
// Here is some code to get your started
//
// agent.customizeCollection('products', (collection: CollectionCustomizer<Schema, 'products'>) => {
// // Actions are documented here:
// // https://docs.forestadmin.com/developer-guide-agents-nodejs/agent-customization/actions
// collection.addAction('Order new batch from supplier', {
// scope: 'Single', // This action can be triggered product by product
// form: [{ label: 'Quantity', type: 'Number', isRequired: true }],
// execute: async (context, resultBuilder) => {
// const product = await context.getRecord(['id', 'name'])
// const quantity = context.formValues['Quantity'];

// // ... Perform work here ...

// return resultBuilder.success(`Your order for a batch of ${quantity} '${product.name}' was sent`);
// }
// });

// // Search customization is documented here:
// // https://docs.forestadmin.com/developer-guide-agents-nodejs/agent-customization/search
// collection.replaceSearch(searchString => {
// // user has typed a product id, let's only that column
// if (searchString.match(/^prdid[\d]{8}/$))
// return { field: 'id', operator: 'Equal', value: searchString };

// // Otherwise assume that user wants to search for a product by name
// return { field: 'name', operator: 'Contains', value: searchString };
// });
// });

// Expose an HTTP endpoint.
agent.mountOnStandaloneServer(Number(process.env.APPLICATION_PORT));

// Start the agent.
agent.start().catch(error => {
console.error('\x1b[31merror:\x1b[0m Forest Admin agent failed to start\n');
console.error('');
console.error(error.stack);
process.exit(1);
});
function echo(arg0: string) {
throw new Error('Function not implemented.');
}

Loading

0 comments on commit a141c91

Please sign in to comment.