headless_lms_models/
certificate_configurations.rs

1use std::fmt;
2
3use crate::{
4    certificate_configuration_to_requirements::{
5        CertificateAllRequirements, get_all_requirements_for_certificate_configuration,
6    },
7    prelude::*,
8};
9
10#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, sqlx::Type)]
11#[cfg_attr(feature = "ts_rs", derive(TS))]
12#[serde(rename_all = "kebab-case")]
13#[sqlx(type_name = "certificate_paper_size", rename_all = "kebab-case")]
14pub enum PaperSize {
15    HorizontalA4,
16    VerticalA4,
17}
18
19impl PaperSize {
20    pub fn width_px(&self) -> u32 {
21        match self {
22            PaperSize::HorizontalA4 => 3508,
23            PaperSize::VerticalA4 => 2480,
24        }
25    }
26    pub fn height_px(&self) -> u32 {
27        match self {
28            PaperSize::HorizontalA4 => 2480,
29            PaperSize::VerticalA4 => 3508,
30        }
31    }
32}
33
34/// How text should be positioned relative to the given coordinates. See <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/text-anchor>.
35#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy, sqlx::Type)]
36#[cfg_attr(feature = "ts_rs", derive(TS))]
37#[serde(rename_all = "kebab-case")]
38#[sqlx(type_name = "certificate_text_anchor", rename_all = "kebab-case")]
39pub enum CertificateTextAnchor {
40    Start,
41    Middle,
42    End,
43}
44
45impl fmt::Display for CertificateTextAnchor {
46    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47        match self {
48            Self::Start => f.write_str("start"),
49            Self::Middle => f.write_str("middle"),
50            Self::End => f.write_str("end"),
51        }
52    }
53}
54
55#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
56#[cfg_attr(feature = "ts_rs", derive(TS))]
57pub struct CertificateConfigurationAndRequirements {
58    pub certificate_configuration: CertificateConfiguration,
59    pub requirements: CertificateAllRequirements,
60}
61
62#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
63#[cfg_attr(feature = "ts_rs", derive(TS))]
64pub struct CertificateConfiguration {
65    pub id: Uuid,
66    pub created_at: DateTime<Utc>,
67    pub updated_at: DateTime<Utc>,
68    pub deleted_at: Option<DateTime<Utc>>,
69    pub certificate_owner_name_y_pos: String,
70    pub certificate_owner_name_x_pos: String,
71    pub certificate_owner_name_font_size: String,
72    pub certificate_owner_name_text_color: String,
73    pub certificate_owner_name_text_anchor: CertificateTextAnchor,
74    pub certificate_validate_url_y_pos: String,
75    pub certificate_validate_url_x_pos: String,
76    pub certificate_validate_url_font_size: String,
77    pub certificate_validate_url_text_color: String,
78    pub certificate_validate_url_text_anchor: CertificateTextAnchor,
79    pub certificate_date_y_pos: String,
80    pub certificate_date_x_pos: String,
81    pub certificate_date_font_size: String,
82    pub certificate_date_text_color: String,
83    pub certificate_date_text_anchor: CertificateTextAnchor,
84    pub certificate_locale: String,
85    pub paper_size: PaperSize,
86    pub background_svg_path: String,
87    pub background_svg_file_upload_id: Uuid,
88    pub overlay_svg_path: Option<String>,
89    pub overlay_svg_file_upload_id: Option<Uuid>,
90    pub render_certificate_grade: bool,
91    pub certificate_grade_y_pos: Option<String>,
92    pub certificate_grade_x_pos: Option<String>,
93    pub certificate_grade_font_size: Option<String>,
94    pub certificate_grade_text_color: Option<String>,
95    pub certificate_grade_text_anchor: Option<CertificateTextAnchor>,
96}
97
98pub async fn get_required_course_instance_ids(
99    conn: &mut PgConnection,
100    certificate_configuration_id: Uuid,
101) -> ModelResult<Vec<Uuid>> {
102    let res = sqlx::query!(
103        r#"
104SELECT course_instance_id
105FROM certificate_configuration_to_requirements
106WHERE certificate_configuration_id = $1
107  AND deleted_at IS NULL
108    "#,
109        certificate_configuration_id,
110    )
111    .fetch_all(&mut *conn)
112    .await?;
113    Ok(res.iter().filter_map(|r| r.course_instance_id).collect())
114}
115
116pub async fn get_by_id(conn: &mut PgConnection, id: Uuid) -> ModelResult<CertificateConfiguration> {
117    let res = sqlx::query_as!(
118        CertificateConfiguration,
119        r#"
120SELECT cc.id,
121  cc.created_at,
122  cc.updated_at,
123  cc.deleted_at,
124  cc.certificate_owner_name_y_pos,
125  cc.certificate_owner_name_x_pos,
126  cc.certificate_owner_name_font_size,
127  cc.certificate_owner_name_text_color,
128  cc.certificate_owner_name_text_anchor AS "certificate_owner_name_text_anchor: _",
129  cc.certificate_validate_url_y_pos,
130  cc.certificate_validate_url_x_pos,
131  cc.certificate_validate_url_font_size,
132  cc.certificate_validate_url_text_color,
133  cc.certificate_validate_url_text_anchor AS "certificate_validate_url_text_anchor: _",
134  cc.certificate_date_y_pos,
135  cc.certificate_date_x_pos,
136  cc.certificate_date_font_size,
137  cc.certificate_date_text_color,
138  cc.certificate_date_text_anchor AS "certificate_date_text_anchor: _",
139  cc.certificate_locale,
140  cc.paper_size AS "paper_size: _",
141  cc.background_svg_path,
142  cc.background_svg_file_upload_id,
143  cc.overlay_svg_path,
144  cc.overlay_svg_file_upload_id,
145  cc.render_certificate_grade,
146  cc.certificate_grade_y_pos,
147  cc.certificate_grade_x_pos,
148  cc.certificate_grade_font_size,
149  cc.certificate_grade_text_color,
150  cc.certificate_grade_text_anchor AS "certificate_grade_text_anchor: _"
151FROM certificate_configurations cc
152WHERE id = $1
153  AND cc.deleted_at IS NULL "#,
154        id,
155    )
156    .fetch_one(&mut *conn)
157    .await?;
158    Ok(res)
159}
160
161pub async fn get_default_configuration_by_course_module_and_course_instance(
162    conn: &mut PgConnection,
163    course_module_id: Uuid,
164    course_instance_id: Option<Uuid>,
165) -> ModelResult<CertificateConfiguration> {
166    let all_certificate_configurations = sqlx::query_as!(
167        CertificateConfiguration,
168        r#"
169SELECT cc.id,
170  cc.created_at,
171  cc.updated_at,
172  cc.deleted_at,
173  cc.certificate_owner_name_y_pos,
174  cc.certificate_owner_name_x_pos,
175  cc.certificate_owner_name_font_size,
176  cc.certificate_owner_name_text_color,
177  cc.certificate_owner_name_text_anchor as "certificate_owner_name_text_anchor: _",
178  cc.certificate_validate_url_y_pos,
179  cc.certificate_validate_url_x_pos,
180  cc.certificate_validate_url_font_size,
181  cc.certificate_validate_url_text_color,
182  cc.certificate_validate_url_text_anchor as "certificate_validate_url_text_anchor: _",
183  cc.certificate_date_y_pos,
184  cc.certificate_date_x_pos,
185  cc.certificate_date_font_size,
186  cc.certificate_date_text_color,
187  cc.certificate_date_text_anchor as "certificate_date_text_anchor: _",
188  cc.certificate_locale,
189  cc.paper_size as "paper_size: _",
190  cc.background_svg_path,
191  cc.background_svg_file_upload_id,
192  cc.overlay_svg_path,
193  cc.overlay_svg_file_upload_id,
194  cc.render_certificate_grade,
195  cc.certificate_grade_y_pos,
196  cc.certificate_grade_x_pos,
197  cc.certificate_grade_font_size,
198  cc.certificate_grade_text_color,
199  cc.certificate_grade_text_anchor AS "certificate_grade_text_anchor: _"
200FROM certificate_configurations cc
201JOIN certificate_configuration_to_requirements cctr ON cc.id = cctr.certificate_configuration_id
202WHERE cctr.course_module_id = $1
203  AND cc.deleted_at IS NULL
204  AND cctr.deleted_at IS NULL
205        "#,
206        course_module_id,
207    )
208    .fetch_all(&mut *conn)
209    .await?;
210    if let Some(course_instance_id) = course_instance_id {
211        // Try to return a course instance specific configuration
212        // The number of certificate configurations should be relatively small, so it should be fine to loop here
213        for certificate_configuration in &all_certificate_configurations {
214            let requirements = get_all_requirements_for_certificate_configuration(
215                conn,
216                certificate_configuration.id,
217            )
218            .await?;
219
220            if requirements.is_default_certificate_configuration()
221                && requirements.course_instance_ids.first() == Some(&course_instance_id)
222            {
223                return Ok(certificate_configuration.clone());
224            }
225        }
226    }
227
228    // Try to return any configuration that applies for the whole course module regardless of the course instance
229    // TODO: Is this needed?
230    // if let Some(config) = res.iter().find(|c| c.course_instance_id.is_none()) {
231    //     return Ok(config.clone());
232    // }
233    Err(ModelError::new(
234        ModelErrorType::RecordNotFound,
235        "No certificate configuration found for the course module or the course instance"
236            .to_string(),
237        None,
238    ))
239}
240
241/** A default certificate configuration requires only one course module. */
242pub async fn get_default_certificate_configurations_and_requirements_by_course_instance(
243    conn: &mut PgConnection,
244    course_instance_id: Uuid,
245) -> ModelResult<Vec<CertificateConfigurationAndRequirements>> {
246    let mut res = Vec::new();
247    let all_certificate_configurations = sqlx::query_as!(
248        CertificateConfiguration,
249        r#"
250SELECT cc.id,
251  cc.created_at,
252  cc.updated_at,
253  cc.deleted_at,
254  cc.certificate_owner_name_y_pos,
255  cc.certificate_owner_name_x_pos,
256  cc.certificate_owner_name_font_size,
257  cc.certificate_owner_name_text_color,
258  cc.certificate_owner_name_text_anchor as "certificate_owner_name_text_anchor: _",
259  cc.certificate_validate_url_y_pos,
260  cc.certificate_validate_url_x_pos,
261  cc.certificate_validate_url_font_size,
262  cc.certificate_validate_url_text_color,
263  cc.certificate_validate_url_text_anchor as "certificate_validate_url_text_anchor: _",
264  cc.certificate_date_y_pos,
265  cc.certificate_date_x_pos,
266  cc.certificate_date_font_size,
267  cc.certificate_date_text_color,
268  cc.certificate_date_text_anchor as "certificate_date_text_anchor: _",
269  cc.certificate_locale,
270  cc.paper_size as "paper_size: _",
271  cc.background_svg_path,
272  cc.background_svg_file_upload_id,
273  cc.overlay_svg_path,
274  cc.overlay_svg_file_upload_id,
275  cc.render_certificate_grade,
276  cc.certificate_grade_y_pos,
277  cc.certificate_grade_x_pos,
278  cc.certificate_grade_font_size,
279  cc.certificate_grade_text_color,
280  cc.certificate_grade_text_anchor AS "certificate_grade_text_anchor: _"
281FROM certificate_configurations cc
282JOIN certificate_configuration_to_requirements cctr ON cc.id = cctr.certificate_configuration_id
283WHERE cctr.course_instance_id = $1
284  AND cc.deleted_at IS NULL
285  AND cctr.deleted_at IS NULL
286        "#,
287        course_instance_id,
288    )
289    .fetch_all(&mut *conn)
290    .await?;
291    // The number of certificate configurations should be relatively small, so it should be fine to loop here
292    for certificate_configuration in &all_certificate_configurations {
293        let requirements =
294            get_all_requirements_for_certificate_configuration(conn, certificate_configuration.id)
295                .await?;
296        if requirements.is_default_certificate_configuration() {
297            res.push(CertificateConfigurationAndRequirements {
298                certificate_configuration: certificate_configuration.clone(),
299                requirements,
300            });
301        }
302    }
303    Ok(res)
304}
305
306/** Finds all configurations that applies to any instance of a course module and requires only one course module. **/
307pub async fn get_all_certifcate_configurations_requiring_only_one_module_and_no_course_instance(
308    conn: &mut PgConnection,
309    course_module_ids: &[Uuid],
310) -> ModelResult<Vec<CertificateConfigurationAndRequirements>> {
311    let mut res = Vec::new();
312    let cadidate_certificate_configurations = sqlx::query_as!(
313        CertificateConfiguration,
314        r#"
315SELECT cc.id,
316  cc.created_at,
317  cc.updated_at,
318  cc.deleted_at,
319  cc.certificate_owner_name_y_pos,
320  cc.certificate_owner_name_x_pos,
321  cc.certificate_owner_name_font_size,
322  cc.certificate_owner_name_text_color,
323  cc.certificate_owner_name_text_anchor as "certificate_owner_name_text_anchor: _",
324  cc.certificate_validate_url_y_pos,
325  cc.certificate_validate_url_x_pos,
326  cc.certificate_validate_url_font_size,
327  cc.certificate_validate_url_text_color,
328  cc.certificate_validate_url_text_anchor as "certificate_validate_url_text_anchor: _",
329  cc.certificate_date_y_pos,
330  cc.certificate_date_x_pos,
331  cc.certificate_date_font_size,
332  cc.certificate_date_text_color,
333  cc.certificate_date_text_anchor as "certificate_date_text_anchor: _",
334  cc.certificate_locale,
335  cc.paper_size as "paper_size: _",
336  cc.background_svg_path,
337  cc.background_svg_file_upload_id,
338  cc.overlay_svg_path,
339  cc.overlay_svg_file_upload_id,
340  cc.render_certificate_grade,
341  cc.certificate_grade_y_pos,
342  cc.certificate_grade_x_pos,
343  cc.certificate_grade_font_size,
344  cc.certificate_grade_text_color,
345  cc.certificate_grade_text_anchor as "certificate_grade_text_anchor: _"
346FROM certificate_configurations cc
347JOIN certificate_configuration_to_requirements cctr ON cc.id = cctr.certificate_configuration_id
348WHERE cctr.course_module_id = ANY($1)
349  AND cctr.course_instance_id IS NULL
350  AND cc.deleted_at IS NULL
351  AND cctr.deleted_at IS NULL
352        "#,
353        course_module_ids,
354    )
355    .fetch_all(&mut *conn)
356    .await?;
357    for certificate_configuration in &cadidate_certificate_configurations {
358        let requirements =
359            get_all_requirements_for_certificate_configuration(conn, certificate_configuration.id)
360                .await?;
361        if requirements.requires_only_one_course_module_and_does_not_require_course_instance() {
362            res.push(CertificateConfigurationAndRequirements {
363                certificate_configuration: certificate_configuration.clone(),
364                requirements,
365            });
366        }
367    }
368    Ok(res)
369}
370
371#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
372#[cfg_attr(feature = "ts_rs", derive(TS))]
373pub struct DatabaseCertificateConfiguration {
374    pub id: Uuid,
375    pub certificate_owner_name_y_pos: Option<String>,
376    pub certificate_owner_name_x_pos: Option<String>,
377    pub certificate_owner_name_font_size: Option<String>,
378    pub certificate_owner_name_text_color: Option<String>,
379    pub certificate_owner_name_text_anchor: Option<CertificateTextAnchor>,
380    pub certificate_validate_url_y_pos: Option<String>,
381    pub certificate_validate_url_x_pos: Option<String>,
382    pub certificate_validate_url_font_size: Option<String>,
383    pub certificate_validate_url_text_color: Option<String>,
384    pub certificate_validate_url_text_anchor: Option<CertificateTextAnchor>,
385    pub certificate_date_y_pos: Option<String>,
386    pub certificate_date_x_pos: Option<String>,
387    pub certificate_date_font_size: Option<String>,
388    pub certificate_date_text_color: Option<String>,
389    pub certificate_date_text_anchor: Option<CertificateTextAnchor>,
390    pub certificate_locale: Option<String>,
391    pub paper_size: Option<PaperSize>,
392    pub background_svg_path: String,
393    pub background_svg_file_upload_id: Uuid,
394    pub overlay_svg_path: Option<String>,
395    pub overlay_svg_file_upload_id: Option<Uuid>,
396    pub render_certificate_grade: bool,
397    pub certificate_grade_y_pos: Option<String>,
398    pub certificate_grade_x_pos: Option<String>,
399    pub certificate_grade_font_size: Option<String>,
400    pub certificate_grade_text_color: Option<String>,
401    pub certificate_grade_text_anchor: Option<CertificateTextAnchor>,
402}
403
404impl DatabaseCertificateConfiguration {
405    /// Uses the same default values as the `CREATE TABLE` statement for `certificate_configurations`.
406    /// A little inconvenient, if there's a better way to turn a None into the default value for the row this can be refactored out.
407    fn build(&self) -> DatabaseCertificateConfigurationInner<'_> {
408        DatabaseCertificateConfigurationInner {
409            id: self.id,
410            certificate_owner_name_y_pos: self
411                .certificate_owner_name_y_pos
412                .as_deref()
413                .unwrap_or("70%"),
414            certificate_owner_name_x_pos: self
415                .certificate_owner_name_x_pos
416                .as_deref()
417                .unwrap_or("50%"),
418            certificate_owner_name_font_size: self
419                .certificate_owner_name_font_size
420                .as_deref()
421                .unwrap_or("150px"),
422            certificate_owner_name_text_color: self
423                .certificate_owner_name_text_color
424                .as_deref()
425                .unwrap_or("black"),
426            certificate_owner_name_text_anchor: self
427                .certificate_owner_name_text_anchor
428                .unwrap_or(CertificateTextAnchor::Middle),
429            certificate_validate_url_y_pos: self
430                .certificate_validate_url_y_pos
431                .as_deref()
432                .unwrap_or("80%"),
433            certificate_validate_url_x_pos: self
434                .certificate_validate_url_x_pos
435                .as_deref()
436                .unwrap_or("88.5%"),
437            certificate_validate_url_font_size: self
438                .certificate_validate_url_font_size
439                .as_deref()
440                .unwrap_or("30px"),
441            certificate_validate_url_text_color: self
442                .certificate_validate_url_text_color
443                .as_deref()
444                .unwrap_or("black"),
445            certificate_validate_url_text_anchor: self
446                .certificate_validate_url_text_anchor
447                .unwrap_or(CertificateTextAnchor::End),
448            certificate_date_y_pos: self.certificate_date_y_pos.as_deref().unwrap_or("88.5%"),
449            certificate_date_x_pos: self.certificate_date_x_pos.as_deref().unwrap_or("15%"),
450            certificate_date_font_size: self
451                .certificate_date_font_size
452                .as_deref()
453                .unwrap_or("30px"),
454            certificate_date_text_color: self
455                .certificate_date_text_color
456                .as_deref()
457                .unwrap_or("black"),
458            certificate_date_text_anchor: self
459                .certificate_date_text_anchor
460                .unwrap_or(CertificateTextAnchor::Start),
461            certificate_locale: self.certificate_locale.as_deref().unwrap_or("en"),
462            paper_size: self.paper_size.unwrap_or(PaperSize::HorizontalA4),
463            background_svg_path: &self.background_svg_path,
464            background_svg_file_upload_id: self.background_svg_file_upload_id,
465            overlay_svg_path: self.overlay_svg_path.as_deref(),
466            overlay_svg_file_upload_id: self.overlay_svg_file_upload_id,
467            render_certificate_grade: self.render_certificate_grade,
468            certificate_grade_y_pos: self.certificate_grade_y_pos.as_deref().unwrap_or(""),
469            certificate_grade_x_pos: self.certificate_grade_x_pos.as_deref().unwrap_or(""),
470            certificate_grade_font_size: self.certificate_grade_font_size.as_deref().unwrap_or(""),
471            certificate_grade_text_color: self
472                .certificate_grade_text_color
473                .as_deref()
474                .unwrap_or(""),
475            certificate_grade_text_anchor: self
476                .certificate_grade_text_anchor
477                .unwrap_or(CertificateTextAnchor::Middle),
478        }
479    }
480}
481
482#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
483struct DatabaseCertificateConfigurationInner<'a> {
484    pub id: Uuid,
485    pub certificate_owner_name_y_pos: &'a str,
486    pub certificate_owner_name_x_pos: &'a str,
487    pub certificate_owner_name_font_size: &'a str,
488    pub certificate_owner_name_text_color: &'a str,
489    pub certificate_owner_name_text_anchor: CertificateTextAnchor,
490    pub certificate_validate_url_y_pos: &'a str,
491    pub certificate_validate_url_x_pos: &'a str,
492    pub certificate_validate_url_font_size: &'a str,
493    pub certificate_validate_url_text_color: &'a str,
494    pub certificate_validate_url_text_anchor: CertificateTextAnchor,
495    pub certificate_date_y_pos: &'a str,
496    pub certificate_date_x_pos: &'a str,
497    pub certificate_date_font_size: &'a str,
498    pub certificate_date_text_color: &'a str,
499    pub certificate_date_text_anchor: CertificateTextAnchor,
500    pub certificate_locale: &'a str,
501    pub paper_size: PaperSize,
502    pub background_svg_path: &'a str,
503    pub background_svg_file_upload_id: Uuid,
504    pub overlay_svg_path: Option<&'a str>,
505    pub overlay_svg_file_upload_id: Option<Uuid>,
506    pub render_certificate_grade: bool,
507    pub certificate_grade_y_pos: &'a str,
508    pub certificate_grade_x_pos: &'a str,
509    pub certificate_grade_font_size: &'a str,
510    pub certificate_grade_text_color: &'a str,
511    pub certificate_grade_text_anchor: CertificateTextAnchor,
512}
513
514pub async fn insert(
515    conn: &mut PgConnection,
516    conf: &DatabaseCertificateConfiguration,
517) -> ModelResult<CertificateConfiguration> {
518    let conf = conf.build();
519    let configuration = sqlx::query_as!(
520        CertificateConfiguration,
521        r#"
522INSERT INTO public.certificate_configurations (
523    certificate_owner_name_y_pos,
524    certificate_owner_name_x_pos,
525    certificate_owner_name_font_size,
526    certificate_owner_name_text_color,
527    certificate_owner_name_text_anchor,
528    certificate_validate_url_y_pos,
529    certificate_validate_url_x_pos,
530    certificate_validate_url_font_size,
531    certificate_validate_url_text_color,
532    certificate_validate_url_text_anchor,
533    certificate_date_y_pos,
534    certificate_date_x_pos,
535    certificate_date_font_size,
536    certificate_date_text_color,
537    certificate_date_text_anchor,
538    certificate_locale,
539    paper_size,
540    background_svg_path,
541    background_svg_file_upload_id,
542    overlay_svg_path,
543    overlay_svg_file_upload_id,
544    render_certificate_grade,
545    certificate_grade_y_pos,
546    certificate_grade_x_pos,
547    certificate_grade_font_size,
548    certificate_grade_text_color,
549    certificate_grade_text_anchor
550  )
551VALUES (
552    $1,
553    $2,
554    $3,
555    $4,
556    $5,
557    $6,
558    $7,
559    $8,
560    $9,
561    $10,
562    $11,
563    $12,
564    $13,
565    $14,
566    $15,
567    $16,
568    $17,
569    $18,
570    $19,
571    $20,
572    $21,
573    $22,
574    $23,
575    $24,
576    $25,
577    $26,
578    $27
579  )
580RETURNING id,
581  created_at,
582  updated_at,
583  deleted_at,
584  certificate_owner_name_y_pos,
585  certificate_owner_name_x_pos,
586  certificate_owner_name_font_size,
587  certificate_owner_name_text_color,
588  certificate_owner_name_text_anchor as "certificate_owner_name_text_anchor: _",
589  certificate_validate_url_y_pos,
590  certificate_validate_url_x_pos,
591  certificate_validate_url_font_size,
592  certificate_validate_url_text_color,
593  certificate_validate_url_text_anchor as "certificate_validate_url_text_anchor: _",
594  certificate_date_y_pos,
595  certificate_date_x_pos,
596  certificate_date_font_size,
597  certificate_date_text_color,
598  certificate_date_text_anchor as "certificate_date_text_anchor: _",
599  certificate_locale,
600  paper_size as "paper_size: _",
601  background_svg_path,
602  background_svg_file_upload_id,
603  overlay_svg_path,
604  overlay_svg_file_upload_id,
605  render_certificate_grade,
606  certificate_grade_y_pos,
607  certificate_grade_x_pos,
608  certificate_grade_font_size,
609  certificate_grade_text_color,
610  certificate_grade_text_anchor as "certificate_grade_text_anchor: _"
611"#,
612        conf.certificate_owner_name_y_pos,
613        conf.certificate_owner_name_x_pos,
614        conf.certificate_owner_name_font_size,
615        conf.certificate_owner_name_text_color,
616        conf.certificate_owner_name_text_anchor as CertificateTextAnchor,
617        conf.certificate_validate_url_y_pos,
618        conf.certificate_validate_url_x_pos,
619        conf.certificate_validate_url_font_size,
620        conf.certificate_validate_url_text_color,
621        conf.certificate_validate_url_text_anchor as CertificateTextAnchor,
622        conf.certificate_date_y_pos,
623        conf.certificate_date_x_pos,
624        conf.certificate_date_font_size,
625        conf.certificate_date_text_color,
626        conf.certificate_date_text_anchor as CertificateTextAnchor,
627        conf.certificate_locale,
628        conf.paper_size as PaperSize,
629        conf.background_svg_path,
630        conf.background_svg_file_upload_id,
631        conf.overlay_svg_path,
632        conf.overlay_svg_file_upload_id,
633        conf.render_certificate_grade,
634        conf.certificate_grade_y_pos,
635        conf.certificate_grade_x_pos,
636        conf.certificate_grade_font_size,
637        conf.certificate_grade_text_color,
638        conf.certificate_grade_text_anchor as CertificateTextAnchor
639    )
640    .fetch_one(conn)
641    .await?;
642    Ok(configuration)
643}
644
645pub async fn update(
646    conn: &mut PgConnection,
647    id: Uuid,
648    conf: &DatabaseCertificateConfiguration,
649) -> ModelResult<()> {
650    let conf = conf.build();
651    sqlx::query!(
652        r#"
653UPDATE public.certificate_configurations
654SET certificate_owner_name_y_pos = $1,
655  certificate_owner_name_x_pos = $2,
656  certificate_owner_name_font_size = $3,
657  certificate_owner_name_text_color = $4,
658  certificate_owner_name_text_anchor = $5,
659  certificate_validate_url_y_pos = $6,
660  certificate_validate_url_x_pos = $7,
661  certificate_validate_url_font_size = $8,
662  certificate_validate_url_text_color = $9,
663  certificate_validate_url_text_anchor = $10,
664  certificate_date_y_pos = $11,
665  certificate_date_x_pos = $12,
666  certificate_date_font_size = $13,
667  certificate_date_text_color = $14,
668  certificate_date_text_anchor = $15,
669  certificate_locale = $16,
670  paper_size = $17,
671  background_svg_path = $18,
672  background_svg_file_upload_id = $19,
673  overlay_svg_path = $20,
674  overlay_svg_file_upload_id = $21,
675  render_certificate_grade = $22,
676  certificate_grade_y_pos = $23,
677  certificate_grade_x_pos = $24,
678  certificate_grade_font_size = $25,
679  certificate_grade_text_color = $26,
680  certificate_grade_text_anchor = $27
681WHERE id = $28
682"#,
683        conf.certificate_owner_name_y_pos,
684        conf.certificate_owner_name_x_pos,
685        conf.certificate_owner_name_font_size,
686        conf.certificate_owner_name_text_color,
687        conf.certificate_owner_name_text_anchor as CertificateTextAnchor,
688        conf.certificate_validate_url_y_pos,
689        conf.certificate_validate_url_x_pos,
690        conf.certificate_validate_url_font_size,
691        conf.certificate_validate_url_text_color,
692        conf.certificate_validate_url_text_anchor as CertificateTextAnchor,
693        conf.certificate_date_y_pos,
694        conf.certificate_date_x_pos,
695        conf.certificate_date_font_size,
696        conf.certificate_date_text_color,
697        conf.certificate_date_text_anchor as CertificateTextAnchor,
698        conf.certificate_locale,
699        conf.paper_size as PaperSize,
700        conf.background_svg_path,
701        conf.background_svg_file_upload_id,
702        conf.overlay_svg_path,
703        conf.overlay_svg_file_upload_id,
704        conf.render_certificate_grade,
705        conf.certificate_grade_y_pos,
706        conf.certificate_grade_x_pos,
707        conf.certificate_grade_font_size,
708        conf.certificate_grade_text_color,
709        conf.certificate_grade_text_anchor as CertificateTextAnchor,
710        id
711    )
712    .execute(conn)
713    .await?;
714    Ok(())
715}
716
717pub async fn delete(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
718    sqlx::query!(
719        "
720UPDATE certificate_configurations
721SET deleted_at = now()
722WHERE id = $1
723",
724        id
725    )
726    .execute(conn)
727    .await?;
728    Ok(())
729}