Flexso LMS FTP
Fetching and caching LMS CSV data from FTP server
Setup
Example from the Extension factory - Training Dashboard
- connector : Is needed because we need to read out a destination to get the FTP credentials to put it into the connectionConfig.
import { DefaultFTPFormatConfig, FTPAdapter, FTPConnectionConfig, DBConfig, DBEntityConfig } from "flexso-lms-ftp";
import { readDestination } from 'sap-cf-destconn';
export class FTPConnector {
private static instance?: FTPConnector;
private adapter: FTPAdapter;
constructor(adapter: FTPAdapter) {
this.adapter = adapter;
if (!FTPConnector.instance) {
FTPConnector.instance = this;
}
FTPConnector.instance.adapter = adapter;
return FTPConnector.instance;
}
// gets records from in mem db if present otherwise fetch data from remote source
public async getRecords(path: string, tenant: string, collectionName: string): Promise<any> {
return this.adapter.getDataFromFile(tenant, collectionName, path);
}
// gets records from in mem db if present otherwise fetch data from remote source with dbEntityConfig
public async getRecordsWithDBEntityConfig(tenant: string, dbEntityConfig: DBEntityConfig): Promise<any> {
return this.getRecords(dbEntityConfig.path, tenant, dbEntityConfig.entityName)
}
public async loadRecords(tenant: string) {
this.adapter.loadDataForTenant(tenant);
}
static async build(tenantName: string, ftpDest: string, dbConfig: DBConfig) {
let destinationConfig = await readDestination(ftpDest, undefined, tenantName);
let _o: any = destinationConfig.destinationConfiguration;
let connectionConfig = new FTPConnectionConfig(
_o.URL.replace(/(^\w+:|^)\/\//, ''),
_o.port,
_o.User,
_o.Password
)
let adapter = new FTPAdapter(tenantName, connectionConfig, new DefaultFTPFormatConfig(), dbConfig);
return new FTPConnector(adapter);
}
}
- Middleware init: We initiate everything here.
export default async function SFMiddleware(req: express.Request, _res: express.Response, next: express.NextFunction) {
try {
let oRequestConfig = getRequestConfig(req);
let tenantName = oRequestConfig.subscribedDomain || '';
let sfsfRequests = new SFSFRequests(oRequestConfig);
// --------------NEW FPT LOGIC--------------------------------
// TODO: get lifetime for each entity from config
// without dir path, files can have a different location
let lifeTime = Constants.DEFAULT_ENTITY_LIFETIME;
let entities = {
ITEMS: new DBEntityConfig("items", lifeTime, await getItemsPath(tenantName)),
CLASSES: new DBEntityConfig("classes", lifeTime, await getClassRequestPath(tenantName)),
SEGMENTS: new DBEntityConfig("segments", lifeTime, await getSegmentRequestPath(tenantName)),
ENROLLMENTS: new DBEntityConfig("enrollments", lifeTime, await getEnrollmentRequestPath(tenantName)),
CLASSREQUESTS: new DBEntityConfig("classRequests", lifeTime, await getItemRequestPath(tenantName))
};
Constants.ENTITIES = entities;
let dbConfig = new DBConfigWithoutDirPath(tenantName, Object.values(entities));
// with dir path, files have a shared parent folder and can't have a diff location
// entityConfig is generated with the dir content to build collections and config.json in folder for stating lifetime (TODO)
/*
let dirPath = await getFTPDirectoryPath();
let dbConfig = new DBConfigWithDirPath(tenantName, dirPath, lifeTime);
*/
let ftpConnector = await FTPConnector.build(tenantName, DESTINATIONS.FTP, dbConfig );
// await ftpConnector.loadRecords(tenantName);
let ftpRequests = new FTPRequests(oRequestConfig, ftpConnector);
//--------------END FTP LOGIC--------------------------------
let currentUser = await sfsfRequests.readCurrentUser(req.user?.id || "", req.headers['accept-language']);
req.sfsfRequests = sfsfRequests;
req.ftpRequests = ftpRequests;
req.currentUser = currentUser;
req.tenantName = tenantName;
next();
} catch (err) {
next(err);
}
}
- insert Middleware The middleware needs to be inserted before your custom routes in Express.
const app = express();
const port = process.env.PORT || 3002; // default port to listen
// add authentication to all requests
app.use(btppassport.initialize());
app.use(btppassport.authenticate("JWT", { session: false }));
// add logging and set the minimum logging level (off, error, warn, info, verbose, debug, silly)
log.setLoggingLevel("info");
// Set custom sink for debugging
if (process.env.debug_session) {
log.setSinkFunction((level: string, output: any) => {
let message = JSON.parse(output);
if (message.request) {
console.log(message.method + " " + message.request);
} else if (message.msg) {
console.log(message.msg);
} else {
console.log(message);
}
});
}
app.use(log.logNetwork);// Bind to express app
// ---------------- INSERT MIDDLEWARE FUNC-----------------
app.use(SFMiddleware);
//---------------------------------------------------------
// routers
app.use("/authentication", authenticationRouter);
app.use("/config", configRouter);
app.use("/sfsf", SFSFRouter);
app.use("/ftp", FTPRouter);
app.use("/i18n", translationRouter);
// if no routes are matched, or there is an error -> show the error!
app.use(handleErrors);
// start the Express server
app.listen(port, () => {
log.info("Server is listening on port %d", port);
});