11import { Request , Response } from "express" ;
22import Survey , { ISurvey } from "../models/surveyModel" ;
3+ import Course from "../models/courseModel" ;
4+ import SurveyResponse from "../models/surveyResponseModel" ;
35
4- // @desc Get the single survey
5- // @route GET /api/surveys
6- // @access Public
7- export const getSurvey = async ( req : Request , res : Response ) : Promise < void > => {
6+ // @desc Get all active surveys
7+ // @route GET /api/surveys?courseId=
8+ export const getSurveys = async ( req : Request , res : Response ) : Promise < void > => {
89 try {
9- // Use `findOneAndUpdate` to find the survey and create one if it doesn't exist
10- const survey = await Survey . findOneAndUpdate (
11- { } , // Find the first document (survey)
12- { $setOnInsert : { questions : [ ] } } , // If no document is found, insert a new one with empty questions
13- { new : true , upsert : true } // Return the updated/new document, and if not found, create it
14- ) . populate ( {
10+ const { courseId } = req . query ;
11+ const filter : any = { isActive : true } ;
12+ if ( courseId ) filter . courseIds = courseId ;
13+
14+ const surveys = await Survey . find ( filter ) . populate ( {
1515 path : "questions" ,
1616 model : "Question" ,
1717 } ) ;
1818
19- res . status ( 200 ) . json ( survey ) ;
19+ res . status ( 200 ) . json ( { success : true , data : surveys } ) ;
2020 } catch ( error ) {
2121 if ( error instanceof Error ) {
22- res . status ( 500 ) . json ( { message : error . message } ) ;
22+ res . status ( 500 ) . json ( { success : false , message : error . message } ) ;
2323 } else {
24- res . status ( 500 ) . json ( { message : "An unknown error occurred" } ) ;
24+ res . status ( 500 ) . json ( { success : false , message : "Internal service error." } ) ;
2525 }
2626 }
2727} ;
2828
29- // @desc Create a new survey (only if it doesn't already exist)
30- // @route POST /api/surveys
31- // @access Public
32- export const createSurvey = async (
33- req : Request ,
34- res : Response
35- ) : Promise < void > => {
29+ // @desc Get survey by ID
30+ // @route GET /api/surveys/:id
31+ export const getSurveyById = async ( req : Request , res : Response ) : Promise < void > => {
3632 try {
37- // Check if a survey already exists
38- const existingSurvey = await Survey . findOne ( ) ;
39-
40- if ( existingSurvey ) {
41- res . status ( 200 ) . json ( {
42- survey : existingSurvey ,
43- message : "Survey already exists. You can update it instead." ,
44- } ) ;
33+ const survey = await Survey . findById ( req . params . id ) . populate ( {
34+ path : "questions" ,
35+ model : "Question" ,
36+ } ) ;
37+
38+ if ( ! survey ) {
39+ res . status ( 404 ) . json ( { success : false , message : "Survey not found." } ) ;
4540 return ;
4641 }
42+
43+ res . status ( 200 ) . json ( { survey} ) ;
44+ } catch ( error ) {
45+ if ( error instanceof Error ) {
46+ res . status ( 500 ) . json ( { success : false , message : error . message } ) ;
47+ } else {
48+ res . status ( 500 ) . json ( { success : false , message : "Internal service error." } ) ;
49+ }
50+ }
51+ } ;
4752
48- const { questions } = req . body ;
53+ // @desc Create new survey
54+ // @route POST /api/surveys
55+ export const createSurvey = async ( req : Request , res : Response ) : Promise < void > => {
56+ try {
57+ const { name, questions, courseIds } = req . body ;
4958
50- if ( ! questions ) {
51- res . status ( 400 ) . json ( { message : "Questions are required" } ) ;
59+ if ( ! name || ! questions ) {
60+ res . status ( 400 ) . json ( { success : false , message : "Name and questions are required." } ) ;
5261 return ;
5362 }
54-
55- // Create a new survey
63+
5664 const survey = new Survey ( {
65+ name,
5766 questions,
67+ courseIds : courseIds || [ ] ,
68+ version : 1 ,
69+ isActive : true ,
5870 } ) ;
5971 await survey . save ( ) ;
6072
61- res . status ( 201 ) . json ( {
62- survey,
63- message : "Survey created successfully" ,
64- } ) ;
73+ // update each linked course's surveyId
74+ if ( courseIds && courseIds . length > 0 ) {
75+ await Course . updateMany (
76+ { _id : { $in : courseIds } } ,
77+ { surveyId : survey . _id }
78+ ) ;
79+ }
80+
81+ res . status ( 201 ) . json ( { success : true , data : survey } ) ;
6582 } catch ( error ) {
6683 if ( error instanceof Error ) {
67- res . status ( 500 ) . json ( { message : error . message } ) ;
84+ res . status ( 500 ) . json ( { success : false , message : error . message } ) ;
6885 } else {
69- res . status ( 500 ) . json ( { message : "An unknown error occurred" } ) ;
86+ res . status ( 500 ) . json ( { success : false , message : "Internal service error." } ) ;
7087 }
7188 }
7289} ;
7390
74- // @desc Update the existing survey
75- // @route PUT /api/surveys
76- // @access Public
77- export const updateSurvey = async (
78- req : Request ,
79- res : Response
80- ) : Promise < void > => {
91+ // @desc Update survey (copy-on-write versioning if responses exist)
92+ // @route PUT /api/surveys/:id
93+ // @body { name?, questions?, courseIdsToUpdate?: string[] }
94+ //
95+ // courseIdsToUpdate controls which courses get the new version.
96+ // Courses not listed keep pointing to the old version.
97+ // If omitted, All linked courses will get the new version.
98+ export const updateSurvey = async ( req : Request , res : Response ) : Promise < void > => {
8199 try {
82- const { questions } = req . body ;
100+ const { name, questions, courseIdsToUpdate} = req . body ;
101+ const survey = await Survey . findById ( req . params . id ) ;
83102
84- console . log ( questions ) ;
103+ if ( ! survey ) {
104+ res . status ( 404 ) . json ( { success : false , message : "Survey not found." } ) ;
105+ return ;
106+ }
85107
86- // Find the single existing survey (since there is only one survey)
87- const survey = await Survey . findOne ( ) ;
108+ // Check if there are existing responses for this survey
109+ const responseCount = await SurveyResponse . countDocuments ( { surveyId : survey . _id } ) ;
88110
89- if ( ! survey ) {
90- res . status ( 404 ) . json ( { message : "Survey not found" } ) ;
111+ if ( responseCount === 0 ) {
112+ // No responses, safe to update in place
113+ if ( name ) survey . name = name ;
114+ if ( questions ) survey . questions = questions ;
115+
116+ await survey . save ( ) ;
117+
118+ const populated = await survey . populate ( { path : "questions" , model : "Question" } ) ;
119+ res . status ( 200 ) . json ( { success : true , data : populated } ) ;
91120 return ;
92121 }
93122
94- // Update the survey with new questions
95- survey . questions = questions ;
123+ const coursesToUpdate = courseIdsToUpdate || survey . courseIds . map ( id => id . toString ( ) ) ;
124+
125+ const coursesStaying = survey . courseIds . map ( id => id . toString ( ) ) . filter ( id => ! coursesToUpdate . includes ( id ) ) ;
126+
127+ const newSurvey = new Survey ( {
128+ name : name || survey . name ,
129+ questions : questions || survey . questions ,
130+ courseIds : coursesToUpdate ,
131+ version : survey . version + 1 ,
132+ parentSurveyId : survey . _id ,
133+ isActive : true ,
134+ } ) ;
135+ await newSurvey . save ( ) ;
136+
137+ // Deactive old survey if no courses remain, otherwise trim its courseIds
138+ if ( coursesStaying . length === 0 ) {
139+ survey . isActive = false ;
140+ survey . courseIds = [ ] ;
141+ } else {
142+ survey . courseIds = coursesStaying as any ;
143+ }
96144 await survey . save ( ) ;
97- console . log ( "survey" , survey ) ;
98145
99- res . status ( 200 ) . json ( survey ) ;
146+ // Point updating courses to new survey
147+ await Course . updateMany (
148+ { _id : { $in : coursesToUpdate } } ,
149+ { surveyId : newSurvey . _id }
150+ ) ;
151+
152+ const populated = await newSurvey . populate ( { path : "questions" , model : "Question" } ) ;
153+ res . status ( 200 ) . json ( { success : true , data : populated } ) ;
154+ } catch ( error ) {
155+ if ( error instanceof Error ) {
156+ res . status ( 500 ) . json ( { success : false , message : error . message } ) ;
157+ } else {
158+ res . status ( 500 ) . json ( { success : false , message : "Internal service error." } ) ;
159+ }
160+ }
161+ } ;
162+
163+ // @desc Delete survey
164+ // @route DELETE /api/surveys/:id
165+ export const deleteSurvey = async ( req : Request , res : Response ) : Promise < void > => {
166+ try {
167+ const survey = await Survey . findById ( req . params . id ) ;
168+
169+ if ( ! survey ) {
170+ res . status ( 404 ) . json ( { success : false , message : "Survey not found." } ) ;
171+ return ;
172+ }
173+
174+ // Unlink from courses
175+ await Course . updateMany ( { surveyId : survey . _id } , { surveyId : null } ) ;
176+
177+ const responseCount = await SurveyResponse . countDocuments ( { surveyId : survey . _id } ) ;
178+ if ( responseCount > 0 ) {
179+ // If there are responses, just mark as inactive instead of deleting
180+ survey . isActive = false ;
181+ survey . courseIds = [ ] ;
182+ await survey . save ( ) ;
183+ } else {
184+ // No responses, safe to delete
185+ await Survey . deleteOne ( { _id : survey . _id } ) ;
186+ }
187+
188+ res . status ( 200 ) . json ( { success : true , message : "Survey deleted successfully." } ) ;
100189 } catch ( error ) {
101190 if ( error instanceof Error ) {
102- console . error ( error ) ;
103- res . status ( 500 ) . json ( { message : error . message } ) ;
191+ res . status ( 500 ) . json ( { success : false , message : error . message } ) ;
104192 } else {
105- res . status ( 500 ) . json ( { message : "An unknown error occurred" } ) ;
193+ res . status ( 500 ) . json ( { success : false , message : "Internal service error." } ) ;
106194 }
107195 }
108196} ;
109197
110- // @desc Delete the survey
111- // @route DELETE /api/surveys
112- // @access Public
113- export const deleteSurvey = async (
114- req : Request ,
115- res : Response
116- ) : Promise < void > => {
198+ // @desc Assign survey to course
199+ // @route POST /api/surveys/:id/assign
200+ // @body {courseId}
201+ export const assignSurvey = async ( req : Request , res : Response ) : Promise < void > => {
117202 try {
118- // Since there is only one survey, no need to look for it by ID
119- const survey = await Survey . findOne ( ) ;
203+ const { courseId } = req . body ;
204+ if ( ! courseId ) {
205+ res . status ( 400 ) . json ( { success : false , message : "courseId is required." } ) ;
206+ return ;
207+ }
120208
209+ const survey = await Survey . findById ( req . params . id ) ;
121210 if ( ! survey ) {
122- res . status ( 404 ) . json ( {
123- success : false ,
124- message : "Survey not found" ,
125- } ) ;
211+ res . status ( 404 ) . json ( { success : false , message : "Survey not found." } ) ;
126212 return ;
127213 }
128214
129- // Delete the survey
130- await Survey . deleteOne ( { _id : survey . _id } ) ;
215+ // Add courseId, avoid duplicates
216+ if ( ! survey . courseIds . includes ( courseId ) ) {
217+ survey . courseIds . push ( courseId ) ;
218+ await survey . save ( ) ;
219+ }
220+
221+ await Course . findByIdAndUpdate ( courseId , { surveyId : survey . _id } ) ;
222+
223+ res . status ( 200 ) . json ( { success : true , data : survey , message : "Survey assigned to course successfully." } ) ;
224+ } catch ( error ) {
225+ if ( error instanceof Error ) {
226+ res . status ( 500 ) . json ( { success : false , message : error . message } ) ;
227+ } else {
228+ res . status ( 500 ) . json ( { success : false , message : "Internal service error." } ) ;
229+ }
230+ }
231+ } ;
131232
132- res . status ( 200 ) . json ( {
133- success : true ,
134- message : "Survey deleted successfully" ,
233+ // @desc Unassign survey from a course
234+ // @route POST /api/surveys/:id/unassign
235+ // @body { courseId }
236+ export const unassignSurvey = async ( req : Request , res : Response ) : Promise < void > => {
237+ try {
238+ const { courseId } = req . body ;
239+ if ( ! courseId ) {
240+ res . status ( 400 ) . json ( { success : false , message : "courseId is required" } ) ;
241+ return ;
242+ }
243+
244+ const survey = await Survey . findById ( req . params . id ) ;
245+ if ( ! survey ) {
246+ res . status ( 404 ) . json ( { success : false , message : "Survey not found" } ) ;
247+ return ;
248+ }
249+
250+ survey . courseIds = survey . courseIds . filter (
251+ ( id : any ) => id . toString ( ) !== courseId
252+ ) as any ;
253+ await survey . save ( ) ;
254+
255+ await Course . findByIdAndUpdate ( courseId , { surveyId : null } ) ;
256+ res . status ( 200 ) . json ( { success : true , data : survey } ) ;
257+ } catch ( error ) {
258+ if ( error instanceof Error ) {
259+ res . status ( 500 ) . json ( { success : false , message : error . message } ) ;
260+ } else {
261+ res . status ( 500 ) . json ( { success : false , message : "An unknown error occurred" } ) ;
262+ }
263+ }
264+ } ;
265+
266+
267+
268+ // @desc Duplicate survey as independent copy
269+ // @route POST /api/surveys/:id/duplicate
270+ // @body { name?, courseId? }
271+ export const duplicateSurvey = async ( req : Request , res : Response ) : Promise < void > => {
272+ try {
273+ const { name, courseId } = req . body ;
274+ const original = await Survey . findById ( req . params . id ) ;
275+
276+ if ( ! original ) {
277+ res . status ( 404 ) . json ( { success : false , message : "Survey not found" } ) ;
278+ return ;
279+ }
280+
281+ const duplicate = new Survey ( {
282+ name : name || `${ original . name } (Copy)` ,
283+ questions : [ ...original . questions ] ,
284+ courseIds : courseId ? [ courseId ] : [ ] ,
285+ version : 1 ,
286+ isActive : true ,
135287 } ) ;
288+ await duplicate . save ( ) ;
289+
290+ if ( courseId ) {
291+ await Course . findByIdAndUpdate ( courseId , { surveyId : duplicate . _id } ) ;
292+ }
293+
294+ res . status ( 201 ) . json ( { success : true , data : duplicate } ) ;
136295 } catch ( error ) {
137296 if ( error instanceof Error ) {
138- res . status ( 500 ) . json ( { message : error . message } ) ;
297+ res . status ( 500 ) . json ( { success : false , message : error . message } ) ;
139298 } else {
140- res . status ( 500 ) . json ( { message : "An unknown error occurred" } ) ;
299+ res . status ( 500 ) . json ( { success : false , message : "An unknown error occurred" } ) ;
141300 }
142301 }
143302} ;
303+
0 commit comments