b05f6b9da0
Jenkins Production Deployment
Co-authored-by: Patrick Müller <mail@pmueller.me> Reviewed-on: #7 Co-authored-by: Patrick Mueller <patrick@mueller-patrick.tech> Co-committed-by: Patrick Mueller <patrick@mueller-patrick.tech>
99 lines
3.1 KiB
TypeScript
99 lines
3.1 KiB
TypeScript
import {NachklangFeedbackDB} from '../Feedback.db';
|
|
import {QuestionType} from '../feedback.interface';
|
|
import {AdminQuestion} from './admin.interface';
|
|
|
|
const mapRow = (row: any): AdminQuestion => ({
|
|
questionId: row.question_id,
|
|
label: row.label,
|
|
helpText: row.help_text,
|
|
questionType: row.question_type,
|
|
isArchived: !!row.is_archived
|
|
});
|
|
|
|
export const listQuestions = async (includeArchived: boolean): Promise<AdminQuestion[]> => {
|
|
let conn = await NachklangFeedbackDB.getConnection();
|
|
try {
|
|
const query = includeArchived
|
|
? 'SELECT * FROM questions ORDER BY created_at DESC'
|
|
: 'SELECT * FROM questions WHERE is_archived = 0 ORDER BY created_at DESC';
|
|
const rows = await conn.query(query);
|
|
return rows.map(mapRow);
|
|
} finally {
|
|
await conn.end();
|
|
}
|
|
};
|
|
|
|
export const createQuestion = async (label: string, helpText: string | null, questionType: QuestionType): Promise<number> => {
|
|
let conn = await NachklangFeedbackDB.getConnection();
|
|
try {
|
|
await conn.beginTransaction();
|
|
const res = await conn.query(
|
|
'INSERT INTO questions (label, help_text, question_type) VALUES (?,?,?) RETURNING question_id',
|
|
[label, helpText, questionType]
|
|
);
|
|
await conn.commit();
|
|
return res[0].question_id;
|
|
} catch (err) {
|
|
await conn.rollback();
|
|
throw err;
|
|
} finally {
|
|
await conn.end();
|
|
}
|
|
};
|
|
|
|
/**
|
|
* Edits label/help text only. question_type is immutable after creation -
|
|
* changing it would invalidate existing answers' question_type_snapshot
|
|
* semantics. The admin UI offers "archive and create new" instead.
|
|
*/
|
|
export const updateQuestion = async (questionId: number, label: string, helpText: string | null): Promise<boolean> => {
|
|
let conn = await NachklangFeedbackDB.getConnection();
|
|
try {
|
|
await conn.beginTransaction();
|
|
const res = await conn.query('UPDATE questions SET label = ?, help_text = ? WHERE question_id = ?', [label, helpText, questionId]);
|
|
await conn.commit();
|
|
return res.affectedRows > 0;
|
|
} catch (err) {
|
|
await conn.rollback();
|
|
throw err;
|
|
} finally {
|
|
await conn.end();
|
|
}
|
|
};
|
|
|
|
export type RemoveQuestionResult = 'ARCHIVED' | 'DELETED' | 'NOT_FOUND';
|
|
|
|
/**
|
|
* Archives (soft delete) a question. Hard-deletes it instead if it has
|
|
* never been assigned to any event, so an admin's typo doesn't have to
|
|
* live forever in the library.
|
|
*/
|
|
export const removeQuestion = async (questionId: number): Promise<RemoveQuestionResult> => {
|
|
let conn = await NachklangFeedbackDB.getConnection();
|
|
try {
|
|
await conn.beginTransaction();
|
|
|
|
const existsRows = await conn.query('SELECT 1 FROM questions WHERE question_id = ?', [questionId]);
|
|
if (existsRows.length === 0) {
|
|
await conn.rollback();
|
|
return 'NOT_FOUND';
|
|
}
|
|
|
|
const usageRows = await conn.query('SELECT 1 FROM event_questions WHERE question_id = ? LIMIT 1', [questionId]);
|
|
if (usageRows.length === 0) {
|
|
await conn.query('DELETE FROM questions WHERE question_id = ?', [questionId]);
|
|
await conn.commit();
|
|
return 'DELETED';
|
|
}
|
|
|
|
await conn.query('UPDATE questions SET is_archived = 1 WHERE question_id = ?', [questionId]);
|
|
await conn.commit();
|
|
return 'ARCHIVED';
|
|
} catch (err) {
|
|
await conn.rollback();
|
|
throw err;
|
|
} finally {
|
|
await conn.end();
|
|
}
|
|
};
|