;;; -*- Mode: Lisp; Syntax: ANSI-Common-Lisp; Base: 10 -*- (declaim (optimize (speed 0) (safety 3) (debug 3))) (in-package :resume) ;; shared (eval-when (:compile-toplevel :load-toplevel :execute) (defun state-options (states) (mapcar (lambda (state) `(:label ,(format nil "~a ~a" (abbr state) (state state)) :value ,(id state))) states)) (defun visastatus-options (visastatuses) (mapcar (lambda (visastatus) `(:label ,(status visastatus) :value ,(id visastatus))) visastatuses)) (defun language-options () (mapcar (lambda (level) `(:label ,level :value ,level)) '("A1" "A2" "B1" "B2" "C1" "C2"))) (defclass resume/resume-service (auth-service) ((title :initarg :title :initform nil :accessor title)) (:documentation "")) (defclass resume/resume-component/view-service (resume/resume-service) ((components :initarg components :initform nil :accessor components) (location-p :initform nil)) (:documentation "")) (defclass resume/resume-component/modify-service (resume/resume-component/view-service) ((form :initarg :form :initform nil :accessor form)) (:documentation ""))) ;; macros (defmacro define-resume-component ((component-s rest-args input-add-fields input-modify-fields order-by &optional view-p)) "`component-s' is a symbol that matches the component's record object name, i.e SKILL, JOB, EDUCATION, LANGUAGE, CONTACTINFO. This will be used all over the place. The record name equals the component name. `rest-args' is the list of REST API arguments for the add and modify forms. In the form like this example: ((id :parameter-type 'integer)) `input-add-fields' and `input-modify-fields' are lists of generic form fields that get inserted into the add and modify forms. `view-p' is `t' if the component has a view in the database because it needs to JOIN with other data." (let* ((component (symbol-name component-s)) (component-lcase (string-downcase component)) (components-s (intern (format nil "~aS" component))) (table (format nil "resume.~a~a" component-lcase (if view-p "_v" ""))) (arg-names (loop for arg in rest-args collect (car arg))) (func-root-s (intern (format nil "~a-JSON" component))) (func-view-s (intern (format nil "~a-VIEW-JSON" component))) (func-add-s (intern (format nil "~a-ADD-JSON" component))) (func-add-submit-s (intern (format nil "~a-ADD-SUBMIT-JSON" component))) (func-modify-s (intern (format nil "~a-MODIFY-JSON" component))) (func-modify-submit-s (intern (format nil "~a-MODIFY-SUBMIT-JSON" component))) (func-delete-s (intern (format nil "~a-DELETE-JSON" component))) (form-add (format nil "~a-add-form" component-lcase)) (form-modify (format nil "~a-modify-form" component-lcase))) `(progn (defun ,func-root-s () (with-auth (instance resume/resume-service "resume-view") (setf (title instance) ,component))) (defun ,func-view-s () (with-auth (instance resume/resume-component/view-service "resume-view") (with-resume-database (let ((resume-pkg (make-instance 'resume-pkg)) (user (get-user))) (setf (components instance) (get-records resume-pkg (make-instance ',component-s :user_id (id user) :*table ,table) ,order-by)))) (sanitize-rest-json instance) (loop for component in (components instance) do (sanitize-json component)))) (defun ,func-add-s () (with-auth (instance resume/resume-component/modify-service "resume-modify") (with-resume-database (let ((resume-pkg (make-instance 'resume-pkg))) (setf (title instance) (format nil "~a - Add" ,component)) (setf (form instance) (make-form ,form-add nil t ,input-add-fields)))))) (defun ,func-add-submit-s (,@arg-names) (declare (special ,@arg-names)) (with-auth (instance resume/resume-component/modify-service "resume-modify") (with-resume-database (handler-case (let* ((resume-pkg (make-instance 'resume-pkg)) (user (get-user)) (,component-s (make-instance ',component-s :user_id (id user)))) (loop for param in (remove-if (lambda (x) (intersection `(,x) '(id))) (sb-introspect:function-lambda-list #',func-add-submit-s)) do (setf (slot-value ,component-s param) (symbol-value param))) (insert-record resume-pkg ,component-s) (setf (session-value :message) (format nil "~a created successfully." ,component))) (error (e) (setf (session-value :errormsg) (format nil "Error creating ~a. ~a" ,component e))))))) (defun ,func-modify-s (id) (with-auth (instance resume/resume-component/modify-service "resume-modify") (setf (title instance) (format nil "~a - Modify" ,component)) (with-resume-database (let* ((resume-pkg (make-instance 'resume-pkg)) (user (get-user)) (,component-s (get-record resume-pkg (make-instance ',component-s :id id :user_id (id user) :*table ,table)))) (if ,component-s (setf (form instance) (make-form ,form-modify nil t ,input-modify-fields)) (setf (session-value :errormsg) (format nil "Error: could not modify ~a. Not found." ,component))))))) (defun ,func-modify-submit-s (,@(append '(id) arg-names)) (declare (special ,@(append '(id) arg-names))) (with-auth (instance resume/resume-component/modify-service "resume-modify") (setf (title instance) (format nil "~a - Modify" ,component)) (with-resume-database (handler-case (let* ((resume-pkg (make-instance 'resume-pkg)) (user (get-user)) (,component-s (get-record resume-pkg (make-instance ',component-s :id id :user_id (id user) :*table ,table)))) (if ,component-s (let ((new-component (make-instance ',component-s))) (loop for param in (sb-introspect:function-lambda-list #',func-modify-submit-s) do (setf (slot-value new-component param) (symbol-value param))) (setf (user_id new-component) (id user)) (update-record resume-pkg new-component) (setf (session-value :message) (format nil "~a modified successfully." ,component))) (error (format nil "~a error" ,component)))) (error (e) (setf (session-value :errormsg) (format nil "Error modifying ~a. ~a" ,component e))))))) (defun ,func-delete-s (id) (with-auth (instance resume/resume-component/modify-service "resume-modify") (with-resume-database (let* ((resume-pkg (make-instance 'resume-pkg)) (user (get-user)) (,component-s (get-record resume-pkg (make-instance ',component-s :id id :user_id (id user) :*table ,table)))) (if ,component-s (progn (delete-record resume-pkg (make-instance ',component-s :id id)) (setf (session-value :message) (format nil "~a deleted successfully." ,component))) (setf (session-value :errormsg) (format nil "Error: could not delete ~a: not found." ,component))))))) (define-endpoint (,(format nil "/resume/~a" component-lcase) :method :get) () ,func-root-s) (define-endpoint (,(format nil "/resume/~a/view" component-lcase) :method :get) () ,func-view-s) (define-endpoint (,(format nil "/resume/~a/add" component-lcase) :method :post) () ,func-add-s) (define-endpoint (,(format nil "/resume/~a/add/submit" component-lcase) :method :post) (&post ,@rest-args) ,func-add-submit-s ,@arg-names) (define-endpoint (,(format nil "/resume/~a/modify" component-lcase) :method :post) (&post (id :parameter-type 'integer)) ,func-modify-s id) (define-endpoint (,(format nil "/resume/~a/modify/submit" component-lcase) :method :post) (&post (id :parameter-type 'integer) ,@rest-args) ,func-modify-submit-s ,@(append '(id) arg-names)) (define-endpoint (,(format nil "/resume/~a/delete/:id" component-lcase) :method :delete) (&path (id 'integer)) ,func-delete-s id)))) (define-resume-component (resume ((name :parameter-type 'string)) `((:name "name" :label "Name" :field-type "text" :required "required") (:label "Add RESUME" :field-type "button" :onclick "on_resume_add_submit_clicked()")) `((:name "id" :field-type "hidden" :value ,(id resume) :required "required") (:name "name" :label "Name" :field-type "text" :value ,(name resume) :required "required") (:label "Modify RESUME" :field-type "button" :onclick "on_resume_modify_submit_clicked()")) "name asc")) (define-resume-component (contactinfo ((address :parameter-type 'string) (city :parameter-type 'string) (state_id :parameter-type 'integer) (phone :parameter-type 'string) (email :parameter-type 'string) (url :parameter-type 'string) (visastatus_id :parameter-type 'integer)) `((:name "address" :label "Address" :field-type "text" :required "required") (:name "city" :label "City" :field-type "text" :required "required") (:name "state_id" :label "State" :field-type "select" :options ,(state-options (get-records resume-pkg (make-instance 'states) "abbr asc")) :required "required") (:name "phone" :label "Phone" :field-type "text" :required "required") (:name "email" :label "Email" :field-type "text" :required "required") (:name "url" :label "URL" :field-type "text") (:name "visastatus_id" :label "VISA Status" :field-type "select" :options ,(visastatus-options (get-records resume-pkg (make-instance 'visastatus) "visastatus asc")) :required "required") (:label "Add CONTACTINFO" :field-type "button" :onclick "on_contactinfo_add_submit_clicked()")) `((:name "id" :field-type "hidden" :value ,(id contactinfo) :required "required") (:name "address" :label "Address" :field-type "text" :value ,(address contactinfo) :required "required") (:name "city" :label "City" :field-type "text" :value ,(city contactinfo) :required "required") (:name "state_id" :label "State" :field-type "select" :value ,(state_id contactinfo) :options ,(state-options (get-records resume-pkg (make-instance 'states) "abbr asc")) :required "required") (:name "phone" :label "Phone" :field-type "text" :value ,(phone contactinfo) :required "required") (:name "email" :label "Email" :field-type "text" :value ,(email contactinfo) :required "required") (:name "url" :label "URL" :field-type "text" :value ,(url contactinfo)) (:name "visastatus_id" :label "VISA Status" :field-type "select" :value (visastatus_id contactinfo) :options ,(visastatus-options (get-records resume-pkg (make-instance 'visastatus) "visastatus asc")) :required "required") (:label "Modify CONTACTINFO" :field-type "button" :onclick "on_contactinfo_modify_submit_clicked()")) "state_abbr asc, city asc, address asc" t)) (define-resume-component (education ((school :parameter-type 'string) (major :parameter-type 'string) (minor :parameter-type 'string) (degree :parameter-type 'string) (progress :parameter-type 'string) (start_date :parameter-type 'string) (end_date :parameter-type 'string)) `((:name "school" :label "School" :field-type "text" :required "required") (:name "major" :label "Major" :field-type "text" :required "required") (:name "minor" :label "Minor" :field-type "text") (:name "degree" :label "Degree" :field-type "text" :required "required") (:name "progress" :label "Progress" :field-type "text" :required "required") (:name "start_date" :label "Start Date" :field-type "text" :required "required") (:name "end_date" :label "End Date" :field-type "text" :required "required") (:label "Add EDUCATION" :field-type "button" :onclick "on_education_add_submit_clicked()")) `((:name "id" :field-type "hidden" :value ,(id education) :required "required") (:name "school" :label "School" :field-type "text" :value ,(school education) :required "required") (:name "major" :label "Major" :field-type "text" :value ,(major education) :required "required") (:name "minor" :label "Minor" :field-type "text" :value ,(minor education)) (:name "degree" :label "Degree" :field-type "text" :value ,(degree education) :required "required") (:name "progress" :label "Progress" :field-type "text" :value ,(progress education) :required "required") (:name "start_date" :label "Start Date" :field-type "text" :value ,(start_date education) :required "required") (:name "end_date" :label "End Date" :field-type "text" :value ,(end_date education) :required "required") (:label "Modify EDUCATION" :field-type "button" :onclick "on_education_modify_submit_clicked()")) "end_date desc, start_date desc, school asc, degree asc, major asc, minor asc")) (define-resume-component (language ((name :parameter-type 'string) (understanding_listening :parameter-type 'string) (understanding_reading :parameter-type 'string) (speaking_interaction :parameter-type 'string) (speaking_production :parameter-type 'string) (writing :parameter-type 'string)) `((:name "name" :label "Name" :field-type "text" :required "required") (:name "understanding_listening" :label "Understanding Listening" :field-type "select" :options ,(language-options) :required "required") (:name "understanding_reading" :label "Understanding Reading" :field-type "select" :options ,(language-options) :required "required") (:name "speaking_interaction" :label "Speaking Interaction" :field-type "select" :options ,(language-options) :required "required") (:name "speaking_production" :label "Speaking Production" :field-type "select" :options ,(language-options) :required "required") (:name "writing" :label "Writing" :field-type "select" :options ,(language-options) :required "required") (:label "Add LANGUAGE" :field-type "button" :onclick "on_language_add_submit_clicked()")) `((:name "id" :field-type "hidden" :value ,(id language) :required "required") (:name "name" :label "Name" :field-type "text" :value ,(name language) :required "required") (:name "understanding_listening" :label "Understanding Listening" :field-type "select" :options ,(language-options) :value ,(understanding_listening language) :required "required") (:name "understanding_reading" :label "Understanding Reading" :field-type "select" :options ,(language-options) :value ,(understanding_reading language) :required "required") (:name "speaking_interaction" :label "Speaking Interaction" :field-type "select" :options ,(language-options) :value ,(speaking_interaction language) :required "required") (:name "speaking_production" :label "Speaking Production" :field-type "select" :options ,(language-options) :value ,(speaking_production language) :required "required") (:name "writing" :label "Writing" :field-type "select" :options ,(language-options) :value ,(writing language) :required "required") (:label "Modify LANGUAGE" :field-type "button" :onclick "on_language_modify_submit_clicked()")) "name asc")) (define-resume-component (skill ((skill :parameter-type 'string)) `((:name "skill" :label "Skill" :field-type "text" :required "required") (:label "Add Skill" :field-type "button" :onclick "on_skill_add_submit_clicked()")) `((:name "id" :field-type "hidden" :value ,(id skill) :required "required") (:name "skill" :label "Skill" :field-type "text" :value ,(skill skill) :required "required") (:label "Modify SKILL" :field-type "button" :onclick "on_skill_modify_submit_clicked()")) "skill asc")) (define-resume-component (job ((company :parameter-type 'string) (location :parameter-type 'string) (start_date :parameter-type 'string) (end_date :parameter-type 'string) (overview :parameter-type 'string)) `((:name "company" :label "Company" :field-type "text" :required "required") (:name "location" :label "Location" :field-type "text" :required "required") (:name "start_date" :label "Start Date" :field-type "text" :required "required") (:name "end_date" :label "End Date" :field-type "text" :required "required") (:name "overview" :label "Overview" :field-type "textarea" :required "required") (:label "Add Job" :field-type "button" :onclick "on_job_add_submit_clicked()")) `((:name "id" :field-type "hidden" :value ,(id job) :required "required") (:name "company" :label "Company" :field-type "text" :value ,(company job) :required "required") (:name "location" :label "Location" :field-type "text" :value ,(location job) :required "required") (:name "start_date" :label "Start Date" :field-type "text" :value ,(start_date job) :required "required") (:name "end_date" :label "End Date" :field-type "text" :value ,(end_date job) :required "required") (:name "overview" :label "Overview" :field-type "textarea" :value ,(overview job) :required "required") (:label "Modify JOB" :field-type "button" :onclick "on_job_modify_submit_clicked()")) "end_date desc, start_date desc, company asc"))