diff --git a/components/teach_n_go/actions/create-prospect/create-prospect.mjs b/components/teach_n_go/actions/create-prospect/create-prospect.mjs new file mode 100644 index 0000000000000..0a9a288d76ab4 --- /dev/null +++ b/components/teach_n_go/actions/create-prospect/create-prospect.mjs @@ -0,0 +1,87 @@ +import app from "../../teach_n_go.app.mjs"; + +export default { + key: "teach_n_go-create-prospect", + name: "Create Prospect", + description: "Creates a new prospect inside Teach 'n Go. [See the documentation](https://intercom.help/teach-n-go/en/articles/5750592-prospect-registration-api)", + version: "0.0.1", + type: "action", + props: { + app, + firstName: { + propDefinition: [ + app, + "firstName", + ], + }, + lastName: { + propDefinition: [ + app, + "lastName", + ], + }, + mobilePhone: { + type: "integer", + label: "Mobile Phone", + description: "The prospect's contact number.", + optional: true, + }, + emailAddress: { + propDefinition: [ + app, + "emailAddress", + ], + optional: true, + }, + description: { + type: "string", + label: "Description", + description: "General information you wish to capture.", + optional: true, + }, + gender: { + propDefinition: [ + app, + "gender", + ], + optional: true, + }, + dateOfBirth: { + propDefinition: [ + app, + "dateOfBirth", + ], + optional: true, + }, + courseSubject: { + type: "string", + label: "Course Subject", + description: "The students chosen subject.", + optional: true, + }, + courseLevel: { + type: "string", + label: "Course Level", + description: "The students chosen level.", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.app.createProspect({ + $, + data: { + "fname": this.firstName, + "lname": this.lastName, + "mobile_phone": this.mobilePhone, + "email_address": this.emailAddress, + "description": this.description, + "gender": this.gender, + "date_of_birth": this.dateOfBirth, + "course_subject": this.courseSubject, + "course_level": this.courseLevel, + }, + }); + $.export("$summary", `Successfully created prospect with ID: ${response.data.id}`); + return response; + }, +}; diff --git a/components/teach_n_go/actions/create-student/create-student.mjs b/components/teach_n_go/actions/create-student/create-student.mjs new file mode 100644 index 0000000000000..c3ce761888919 --- /dev/null +++ b/components/teach_n_go/actions/create-student/create-student.mjs @@ -0,0 +1,194 @@ +import { PAYMENT_METHOD_OPTIONS } from "../../common/constants.mjs"; +import app from "../../teach_n_go.app.mjs"; + +export default { + key: "teach_n_go-create-student", + name: "Create Student", + description: "Registers a new student in Teach 'n Go. [See the documentation](https://intercom.help/teach-n-go/en/articles/6807235-new-student-and-class-registration-api)", + version: "0.0.1", + type: "action", + props: { + app, + firstName: { + propDefinition: [ + app, + "firstName", + ], + description: "The student's first name.", + }, + lastName: { + propDefinition: [ + app, + "lastName", + ], + description: "The student's last name.", + }, + gender: { + propDefinition: [ + app, + "gender", + ], + description: "The student's gender.", + optional: true, + }, + registrationDate: { + type: "string", + label: "Registration Date", + description: "The student's registration date. **Format: YYYY-MM-DD**", + optional: true, + }, + dateOfBirth: { + propDefinition: [ + app, + "dateOfBirth", + ], + description: "The student's date of birth. **Format: YYYY-MM-DD**", + optional: true, + }, + identificationNumber: { + type: "string", + label: "Identification Number", + description: "The external number to identify the student.", + optional: true, + }, + preferredPaymentMethod: { + type: "integer", + label: "Preferred Payment Method", + description: "The payment method the student want to use.", + options: PAYMENT_METHOD_OPTIONS, + optional: true, + }, + discountPercentage: { + type: "string", + label: "Discount Percentage", + description: "The discount percentage on the payment amount.", + optional: true, + }, + mobilePhoneCode: { + type: "integer", + label: "Mobile Phone Code", + description: "The region code of the mobile phone. Min length: 2, Max length: 4", + optional: true, + }, + mobilePhone: { + type: "integer", + label: "Mobile Phone", + description: "The student's mobile phone", + optional: true, + }, + homePhoneCode: { + type: "integer", + label: "Home Phone Code", + description: "The region code of the home phone. Min length: 2, Max length: 4", + optional: true, + }, + homePhone: { + type: "integer", + label: "Home Phone", + description: "The student's home phone", + optional: true, + }, + emailAddress: { + propDefinition: [ + app, + "emailAddress", + ], + description: "The student's email address.", + optional: true, + }, + streetNameAndNumber: { + type: "string", + label: "Street Name And Number", + description: "The student's full address.", + optional: true, + }, + flatFloor: { + type: "string", + label: "Flat Floor", + description: "The student's address flat floor if it exists.", + optional: true, + }, + area: { + type: "string", + label: "Area", + description: "The student's address area.", + optional: true, + }, + city: { + type: "string", + label: "City", + description: "The student's city.", + optional: true, + }, + postcode: { + type: "string", + label: "Postcode", + description: "The student's postcode.", + optional: true, + }, + countryCode: { + type: "string", + label: "Country Code", + description: "The student's ISO 2 letter country code, e.g. (US, UK, IN).", + optional: true, + }, + generalNotes: { + type: "string", + label: "General Notes", + description: "Some student's additional notes.", + optional: true, + }, + medicalNotes: { + type: "string", + label: "Medical Notes", + description: "Some student's additional medical notes.", + optional: true, + }, + courses: { + propDefinition: [ + app, + "courses", + ], + optional: true, + }, + enrolmentDate: { + type: "string", + label: "Enrolment Date", + description: "The date of the student's enrolment.", + optional: true, + }, + }, + async run({ $ }) { + const response = await this.app.registerStudent({ + $, + data: { + fname: this.firstName, + lname: this.lastName, + gender: this.gender, + registration_date: this.registrationDate, + date_of_birth: this.dateOfBirth, + identification_number: this.identificationNumber, + preferred_payment_method: this.preferredPaymentMethod, + discount_percentage: this.discountPercentage && parseFloat(this.discountPercentage), + mobile_phone_code: this.mobilePhoneCode, + mobile_phone: this.mobilePhone, + home_phone_code: this.homePhoneCode, + home_phone: this.homePhone, + email_address: this.emailAddress, + street_name_and_number: this.streetNameAndNumber, + flat_floor: this.flatFloor, + area: this.area, + city: this.city, + postcode: this.postcode, + country_code: this.countryCode, + general_notes: this.generalNotes, + medical_notes: this.medicalNotes, + courses: this.courses, + enrolment_date: this.enrolmentDate, + }, + }); + + $.export("$summary", `Successfully registered student with ID: ${response.data.id}`); + return response; + }, +}; diff --git a/components/teach_n_go/common/constants.mjs b/components/teach_n_go/common/constants.mjs new file mode 100644 index 0000000000000..f8b20bd4ccc75 --- /dev/null +++ b/components/teach_n_go/common/constants.mjs @@ -0,0 +1,30 @@ +export const LIMIT = 100; + +export const GENDER_OPTIONS = [ + "Female", + "Male", + "Not Specified", +]; + +export const PAYMENT_METHOD_OPTIONS = [ + { + label: "Cash", + value: 1, + }, + { + label: "Cheque", + value: 2, + }, + { + label: "Credit Card", + value: 3, + }, + { + label: "Bank Transfer", + value: 4, + }, + { + label: "Direct Debit", + value: 5, + }, +]; diff --git a/components/teach_n_go/common/utils.mjs b/components/teach_n_go/common/utils.mjs new file mode 100644 index 0000000000000..dcc9cc61f6f41 --- /dev/null +++ b/components/teach_n_go/common/utils.mjs @@ -0,0 +1,24 @@ +export const parseObject = (obj) => { + if (!obj) return undefined; + + if (Array.isArray(obj)) { + return obj.map((item) => { + if (typeof item === "string") { + try { + return JSON.parse(item); + } catch (e) { + return item; + } + } + return item; + }); + } + if (typeof obj === "string") { + try { + return JSON.parse(obj); + } catch (e) { + return obj; + } + } + return obj; +}; diff --git a/components/teach_n_go/package.json b/components/teach_n_go/package.json index 5a857d1e0c1c2..4db442120a99c 100644 --- a/components/teach_n_go/package.json +++ b/components/teach_n_go/package.json @@ -1,6 +1,6 @@ { "name": "@pipedream/teach_n_go", - "version": "0.0.1", + "version": "0.1.0", "description": "Pipedream Teach 'n Go Components", "main": "teach_n_go.app.mjs", "keywords": [ @@ -11,5 +11,8 @@ "author": "Pipedream (https://pipedream.com/)", "publishConfig": { "access": "public" + }, + "dependencies": { + "@pipedream/platform": "^3.0.3" } -} \ No newline at end of file +} diff --git a/components/teach_n_go/sources/common/base.mjs b/components/teach_n_go/sources/common/base.mjs new file mode 100644 index 0000000000000..6eed1508280e6 --- /dev/null +++ b/components/teach_n_go/sources/common/base.mjs @@ -0,0 +1,59 @@ +import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform"; +import app from "../../teach_n_go.app.mjs"; + +export default { + props: { + app, + db: "$.service.db", + timer: { + type: "$.interface.timer", + default: { + intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL, + }, + }, + }, + methods: { + _getLastId() { + return this.db.get("lastId") || 0; + }, + _setLastId(lastId) { + this.db.set("lastId", lastId); + }, + async emitEvent(maxResults = false) { + const lastId = this._getLastId(); + + const response = this.app.paginate({ + fn: this.getFunction(), + }); + + let responseArray = []; + for await (const item of response) { + if (item.id <= lastId) break; + responseArray.push(item); + } + + if (responseArray.length) { + if (maxResults && (responseArray.length > maxResults)) { + responseArray.length = maxResults; + } + this._setLastId(responseArray[0].id); + } + + for (const item of responseArray.reverse()) { + this.$emit(item, { + id: item.id, + summary: this.getSummary(item), + ts: Date.parse(item.created || new Date()), + }); + } + }, + }, + hooks: { + async deploy() { + await this.emitEvent(25); + }, + }, + async run() { + await this.emitEvent(); + }, +}; diff --git a/components/teach_n_go/sources/new-class/new-class.mjs b/components/teach_n_go/sources/new-class/new-class.mjs new file mode 100644 index 0000000000000..e3dc8d48985b8 --- /dev/null +++ b/components/teach_n_go/sources/new-class/new-class.mjs @@ -0,0 +1,22 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "teach_n_go-new-class", + name: "New Class Created", + description: "Emit new event when a class is created.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getFunction() { + return this.app.listCourses; + }, + getSummary(item) { + return `New Class: ${item.course_full_title}`; + }, + }, + sampleEmit, +}; diff --git a/components/teach_n_go/sources/new-class/test-event.mjs b/components/teach_n_go/sources/new-class/test-event.mjs new file mode 100644 index 0000000000000..6c4f7457f87bf --- /dev/null +++ b/components/teach_n_go/sources/new-class/test-event.mjs @@ -0,0 +1,46 @@ +export default { + "course_title": "Class title", + "course_subject": "Course subject", + "course_level": "Course level", + "school_id": 16385, + "start_date": "2024-10-21", + "end_date": "2024-10-28", + "recurrence": "weekly", + "payment_frequency": "free", + "id": 153319, + "photo": null, + "payment_fee": "0", + "course_description": "", + "course_full_title": "Class title [Online Lesson]", + "created": "2024-10-17 18:20:13", + "modified": "2024-10-17 18:20:15", + "color": "color1", + "course_started": false, + "course_ended": false, + "course_status": 1, + "num_enrolled_students": 0, + "teachers": "66792", + "classrooms": "39627", + "billing_month_start_date": "2024-10-01", + "billing_month_end_date": "2024-10-01", + "custom_payments": null, + "archived": false, + "awarding_body": "", + "course_code": "", + "book_code": "", + "total_lessons": 2, + "total_lessons_hrs": "02:00", + "skype_meeting_link": "", + "year": null, + "credit_hours": "", + "class_type": "", + "is_ended": null, + "teacher_hourly_fees": null, + "is_booking_class": false, + "subscription_plan_id": null, + "is_stripe_sub_allow": 0, + "created_by": 66114, + "modified_by": 66114, + "exception_dates": null, + "removed_exception_dates": null +} \ No newline at end of file diff --git a/components/teach_n_go/sources/new-student/new-student.mjs b/components/teach_n_go/sources/new-student/new-student.mjs new file mode 100644 index 0000000000000..5fe9a2abeb76f --- /dev/null +++ b/components/teach_n_go/sources/new-student/new-student.mjs @@ -0,0 +1,26 @@ +import common from "../common/base.mjs"; +import sampleEmit from "./test-event.mjs"; + +export default { + ...common, + key: "teach_n_go-new-student", + name: "New Student Registration", + description: "Emit new event when a new student is registered.", + version: "0.0.1", + type: "source", + dedupe: "unique", + methods: { + ...common.methods, + getFunction() { + return this.app.listStudents; + }, + getSummary({ + fname, lname, email_address: email, + }) { + return `New Student: ${fname} ${lname}${email + ? ` - ${email}` + : ""}`; + }, + }, + sampleEmit, +}; diff --git a/components/teach_n_go/sources/new-student/test-event.mjs b/components/teach_n_go/sources/new-student/test-event.mjs new file mode 100644 index 0000000000000..a8692d2b06884 --- /dev/null +++ b/components/teach_n_go/sources/new-student/test-event.mjs @@ -0,0 +1,51 @@ +export default { + "id": 411081, + "school_id": 16385, + "identification_number": "123", + "fname": "Jhon", + "lname": "Doe", + "gender": "Male", + "registration_date": "2024-10-17", + "date_of_birth": "1991-02-02", + "email_address": "jhon@email.com", + "mobile_phonecode": "", + "mobile_phone": "", + "home_phonecode": "", + "home_phone": "", + "street_name_and_number": "", + "flat_floor": "", + "area": "", + "city": "", + "country_code": "", + "postcode": "", + "whole_address": "", + "general_notes": "", + "medical_notes": "", + "delayed_payments": "", + "num_enrolled_courses": 1, + "discount_percentage": "10", + "discount_type": "percentage", + "custom_fields": [], + "timezone": null, + "course_subject": null, + "course_level": null, + "photo_link": "/img/user-no-img-m.jpg", + "preferred_payment_method": "Credit Card", + "classes": [ + { + "course_title": "class title", + "course_full_title": "class title [Online Lesson]", + "course_subject": "", + "course_level": "", + "course_description": "", + "course_started": true, + "course_ended": false, + "school_id": 16385, + "recurrence": "weekly", + "payment_frequency": "free", + "enrolment_date": "2024-10-20", + "unenrolment_date": null, + "student_id": 411081 + } + ] +} \ No newline at end of file diff --git a/components/teach_n_go/teach_n_go.app.mjs b/components/teach_n_go/teach_n_go.app.mjs index 732d4c6fc05d7..e86422f456a65 100644 --- a/components/teach_n_go/teach_n_go.app.mjs +++ b/components/teach_n_go/teach_n_go.app.mjs @@ -1,11 +1,122 @@ +import { axios } from "@pipedream/platform"; +import { GENDER_OPTIONS } from "./common/constants.mjs"; + export default { type: "app", app: "teach_n_go", - propDefinitions: {}, + propDefinitions: { + firstName: { + type: "string", + label: "First Name", + description: "The prospect's first name.", + }, + lastName: { + type: "string", + label: "Last Name", + description: "The prospect's surname.", + }, + gender: { + type: "string", + label: "Gender", + description: "The prospect's gender.", + options: GENDER_OPTIONS, + }, + dateOfBirth: { + type: "string", + label: "Date Of Birth", + description: "The prospect's date of birth. **Format: YYYY-MM-DD**", + }, + emailAddress: { + type: "string", + label: "Email Address", + description: "The prospect's email address.", + }, + courses: { + type: "integer[]", + label: "Courses", + description: "The student's course Ids.", + async options() { + const { data } = await this.listCourses(); + + return data.map(({ + course_full_title: label, id: value, + }) => ({ + label, + value, + })); + }, + }, + }, methods: { - // this.$auth contains connected account data - authKeys() { - console.log(Object.keys(this.$auth)); + _baseUrl() { + return "https://app.teachngo.com"; + }, + _headers(headers = {}) { + return { + "X-API-KEY": `${this.$auth.api_key}`, + ...headers, + }; + }, + _makeRequest({ + $ = this, path, headers, ...opts + }) { + return axios($, { + url: this._baseUrl() + path, + headers: this._headers(headers), + ...opts, + }); + }, + createProspect(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/LeadsApi/add", + ...opts, + }); + }, + registerStudent(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/api/student", + ...opts, + }); + }, + listCourses(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/globalApis/course_list", + ...opts, + }); + }, + listStudents(opts = {}) { + return this._makeRequest({ + method: "POST", + path: "/globalApis/student_list", + ...opts, + }); + }, + async *paginate({ + fn, params = {}, ...opts + }) { + let hasMore = false; + let page = 0; + + do { + params.page = ++page; + const { + data, + next, + } = await fn({ + params, + ...opts, + }); + + for (const d of data) { + yield d; + } + + hasMore = next; + + } while (hasMore); }, }, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8e24236382bd7..e4c6e295e7fcc 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -9966,7 +9966,10 @@ importers: specifiers: {} components/teach_n_go: - specifiers: {} + specifiers: + '@pipedream/platform': ^3.0.3 + dependencies: + '@pipedream/platform': 3.0.3 components/teachable: specifiers: