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_by_id(conn: &mut PgConnection, id: Uuid) -> ModelResult<CertificateConfiguration> {
99    let res = sqlx::query_as!(
100        CertificateConfiguration,
101        r#"
102SELECT cc.id,
103  cc.created_at,
104  cc.updated_at,
105  cc.deleted_at,
106  cc.certificate_owner_name_y_pos,
107  cc.certificate_owner_name_x_pos,
108  cc.certificate_owner_name_font_size,
109  cc.certificate_owner_name_text_color,
110  cc.certificate_owner_name_text_anchor AS "certificate_owner_name_text_anchor: _",
111  cc.certificate_validate_url_y_pos,
112  cc.certificate_validate_url_x_pos,
113  cc.certificate_validate_url_font_size,
114  cc.certificate_validate_url_text_color,
115  cc.certificate_validate_url_text_anchor AS "certificate_validate_url_text_anchor: _",
116  cc.certificate_date_y_pos,
117  cc.certificate_date_x_pos,
118  cc.certificate_date_font_size,
119  cc.certificate_date_text_color,
120  cc.certificate_date_text_anchor AS "certificate_date_text_anchor: _",
121  cc.certificate_locale,
122  cc.paper_size AS "paper_size: _",
123  cc.background_svg_path,
124  cc.background_svg_file_upload_id,
125  cc.overlay_svg_path,
126  cc.overlay_svg_file_upload_id,
127  cc.render_certificate_grade,
128  cc.certificate_grade_y_pos,
129  cc.certificate_grade_x_pos,
130  cc.certificate_grade_font_size,
131  cc.certificate_grade_text_color,
132  cc.certificate_grade_text_anchor AS "certificate_grade_text_anchor: _"
133FROM certificate_configurations cc
134WHERE id = $1
135  AND cc.deleted_at IS NULL "#,
136        id,
137    )
138    .fetch_one(&mut *conn)
139    .await?;
140    Ok(res)
141}
142
143pub async fn get_default_configuration_by_course_module(
144    conn: &mut PgConnection,
145    course_module_id: Uuid,
146) -> ModelResult<CertificateConfiguration> {
147    let all_certificate_configurations = sqlx::query_as!(
148        CertificateConfiguration,
149        r#"
150SELECT cc.id,
151  cc.created_at,
152  cc.updated_at,
153  cc.deleted_at,
154  cc.certificate_owner_name_y_pos,
155  cc.certificate_owner_name_x_pos,
156  cc.certificate_owner_name_font_size,
157  cc.certificate_owner_name_text_color,
158  cc.certificate_owner_name_text_anchor as "certificate_owner_name_text_anchor: _",
159  cc.certificate_validate_url_y_pos,
160  cc.certificate_validate_url_x_pos,
161  cc.certificate_validate_url_font_size,
162  cc.certificate_validate_url_text_color,
163  cc.certificate_validate_url_text_anchor as "certificate_validate_url_text_anchor: _",
164  cc.certificate_date_y_pos,
165  cc.certificate_date_x_pos,
166  cc.certificate_date_font_size,
167  cc.certificate_date_text_color,
168  cc.certificate_date_text_anchor as "certificate_date_text_anchor: _",
169  cc.certificate_locale,
170  cc.paper_size as "paper_size: _",
171  cc.background_svg_path,
172  cc.background_svg_file_upload_id,
173  cc.overlay_svg_path,
174  cc.overlay_svg_file_upload_id,
175  cc.render_certificate_grade,
176  cc.certificate_grade_y_pos,
177  cc.certificate_grade_x_pos,
178  cc.certificate_grade_font_size,
179  cc.certificate_grade_text_color,
180  cc.certificate_grade_text_anchor AS "certificate_grade_text_anchor: _"
181FROM certificate_configurations cc
182JOIN certificate_configuration_to_requirements cctr ON cc.id = cctr.certificate_configuration_id
183WHERE cctr.course_module_id = $1
184  AND cc.deleted_at IS NULL
185  AND cctr.deleted_at IS NULL
186        "#,
187        course_module_id,
188    )
189    .fetch_all(&mut *conn)
190    .await?;
191    if all_certificate_configurations.len() > 1 {
192        warn!(
193            "Multiple certificate configurations found for the course module: {:?}",
194            course_module_id
195        );
196    }
197    if !all_certificate_configurations.is_empty() {
198        return Ok(all_certificate_configurations[0].clone());
199    }
200
201    Err(ModelError::new(
202        ModelErrorType::RecordNotFound,
203        "No certificate configuration found for the course module".to_string(),
204        None,
205    ))
206}
207
208/** Returns all default certificate configurations for a course, one per course module. A default certificate configuration requires only one course module. */
209pub async fn get_default_certificate_configurations_and_requirements_by_course(
210    conn: &mut PgConnection,
211    course_id: Uuid,
212) -> ModelResult<Vec<CertificateConfigurationAndRequirements>> {
213    let mut res = Vec::new();
214    let all_certificate_configurations = sqlx::query_as!(
215        CertificateConfiguration,
216        r#"
217SELECT cc.id,
218  cc.created_at,
219  cc.updated_at,
220  cc.deleted_at,
221  cc.certificate_owner_name_y_pos,
222  cc.certificate_owner_name_x_pos,
223  cc.certificate_owner_name_font_size,
224  cc.certificate_owner_name_text_color,
225  cc.certificate_owner_name_text_anchor AS "certificate_owner_name_text_anchor: _",
226  cc.certificate_validate_url_y_pos,
227  cc.certificate_validate_url_x_pos,
228  cc.certificate_validate_url_font_size,
229  cc.certificate_validate_url_text_color,
230  cc.certificate_validate_url_text_anchor AS "certificate_validate_url_text_anchor: _",
231  cc.certificate_date_y_pos,
232  cc.certificate_date_x_pos,
233  cc.certificate_date_font_size,
234  cc.certificate_date_text_color,
235  cc.certificate_date_text_anchor AS "certificate_date_text_anchor: _",
236  cc.certificate_locale,
237  cc.paper_size AS "paper_size: _",
238  cc.background_svg_path,
239  cc.background_svg_file_upload_id,
240  cc.overlay_svg_path,
241  cc.overlay_svg_file_upload_id,
242  cc.render_certificate_grade,
243  cc.certificate_grade_y_pos,
244  cc.certificate_grade_x_pos,
245  cc.certificate_grade_font_size,
246  cc.certificate_grade_text_color,
247  cc.certificate_grade_text_anchor AS "certificate_grade_text_anchor: _"
248FROM certificate_configurations cc
249  JOIN certificate_configuration_to_requirements cctr ON cc.id = cctr.certificate_configuration_id
250WHERE cctr.course_module_id IN (
251    SELECT id
252    FROM course_modules
253    WHERE course_id = $1
254      AND deleted_at IS NULL
255  )
256  AND cc.deleted_at IS NULL
257  AND cctr.deleted_at IS NULL
258        "#,
259        course_id,
260    )
261    .fetch_all(&mut *conn)
262    .await?;
263    // The number of certificate configurations should be relatively small, so it should be fine to loop here
264    for certificate_configuration in &all_certificate_configurations {
265        let requirements =
266            get_all_requirements_for_certificate_configuration(conn, certificate_configuration.id)
267                .await?;
268        if requirements.is_default_certificate_configuration() {
269            res.push(CertificateConfigurationAndRequirements {
270                certificate_configuration: certificate_configuration.clone(),
271                requirements,
272            });
273        }
274    }
275    Ok(res)
276}
277
278#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
279#[cfg_attr(feature = "ts_rs", derive(TS))]
280pub struct DatabaseCertificateConfiguration {
281    pub id: Uuid,
282    pub certificate_owner_name_y_pos: Option<String>,
283    pub certificate_owner_name_x_pos: Option<String>,
284    pub certificate_owner_name_font_size: Option<String>,
285    pub certificate_owner_name_text_color: Option<String>,
286    pub certificate_owner_name_text_anchor: Option<CertificateTextAnchor>,
287    pub certificate_validate_url_y_pos: Option<String>,
288    pub certificate_validate_url_x_pos: Option<String>,
289    pub certificate_validate_url_font_size: Option<String>,
290    pub certificate_validate_url_text_color: Option<String>,
291    pub certificate_validate_url_text_anchor: Option<CertificateTextAnchor>,
292    pub certificate_date_y_pos: Option<String>,
293    pub certificate_date_x_pos: Option<String>,
294    pub certificate_date_font_size: Option<String>,
295    pub certificate_date_text_color: Option<String>,
296    pub certificate_date_text_anchor: Option<CertificateTextAnchor>,
297    pub certificate_locale: Option<String>,
298    pub paper_size: Option<PaperSize>,
299    pub background_svg_path: String,
300    pub background_svg_file_upload_id: Uuid,
301    pub overlay_svg_path: Option<String>,
302    pub overlay_svg_file_upload_id: Option<Uuid>,
303    pub render_certificate_grade: bool,
304    pub certificate_grade_y_pos: Option<String>,
305    pub certificate_grade_x_pos: Option<String>,
306    pub certificate_grade_font_size: Option<String>,
307    pub certificate_grade_text_color: Option<String>,
308    pub certificate_grade_text_anchor: Option<CertificateTextAnchor>,
309}
310
311impl DatabaseCertificateConfiguration {
312    /// Uses the same default values as the `CREATE TABLE` statement for `certificate_configurations`.
313    /// 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.
314    fn build(&self) -> DatabaseCertificateConfigurationInner<'_> {
315        DatabaseCertificateConfigurationInner {
316            id: self.id,
317            certificate_owner_name_y_pos: self
318                .certificate_owner_name_y_pos
319                .as_deref()
320                .unwrap_or("70%"),
321            certificate_owner_name_x_pos: self
322                .certificate_owner_name_x_pos
323                .as_deref()
324                .unwrap_or("50%"),
325            certificate_owner_name_font_size: self
326                .certificate_owner_name_font_size
327                .as_deref()
328                .unwrap_or("150px"),
329            certificate_owner_name_text_color: self
330                .certificate_owner_name_text_color
331                .as_deref()
332                .unwrap_or("black"),
333            certificate_owner_name_text_anchor: self
334                .certificate_owner_name_text_anchor
335                .unwrap_or(CertificateTextAnchor::Middle),
336            certificate_validate_url_y_pos: self
337                .certificate_validate_url_y_pos
338                .as_deref()
339                .unwrap_or("80%"),
340            certificate_validate_url_x_pos: self
341                .certificate_validate_url_x_pos
342                .as_deref()
343                .unwrap_or("88.5%"),
344            certificate_validate_url_font_size: self
345                .certificate_validate_url_font_size
346                .as_deref()
347                .unwrap_or("30px"),
348            certificate_validate_url_text_color: self
349                .certificate_validate_url_text_color
350                .as_deref()
351                .unwrap_or("black"),
352            certificate_validate_url_text_anchor: self
353                .certificate_validate_url_text_anchor
354                .unwrap_or(CertificateTextAnchor::End),
355            certificate_date_y_pos: self.certificate_date_y_pos.as_deref().unwrap_or("88.5%"),
356            certificate_date_x_pos: self.certificate_date_x_pos.as_deref().unwrap_or("15%"),
357            certificate_date_font_size: self
358                .certificate_date_font_size
359                .as_deref()
360                .unwrap_or("30px"),
361            certificate_date_text_color: self
362                .certificate_date_text_color
363                .as_deref()
364                .unwrap_or("black"),
365            certificate_date_text_anchor: self
366                .certificate_date_text_anchor
367                .unwrap_or(CertificateTextAnchor::Start),
368            certificate_locale: self.certificate_locale.as_deref().unwrap_or("en"),
369            paper_size: self.paper_size.unwrap_or(PaperSize::HorizontalA4),
370            background_svg_path: &self.background_svg_path,
371            background_svg_file_upload_id: self.background_svg_file_upload_id,
372            overlay_svg_path: self.overlay_svg_path.as_deref(),
373            overlay_svg_file_upload_id: self.overlay_svg_file_upload_id,
374            render_certificate_grade: self.render_certificate_grade,
375            certificate_grade_y_pos: self.certificate_grade_y_pos.as_deref().unwrap_or(""),
376            certificate_grade_x_pos: self.certificate_grade_x_pos.as_deref().unwrap_or(""),
377            certificate_grade_font_size: self.certificate_grade_font_size.as_deref().unwrap_or(""),
378            certificate_grade_text_color: self
379                .certificate_grade_text_color
380                .as_deref()
381                .unwrap_or(""),
382            certificate_grade_text_anchor: self
383                .certificate_grade_text_anchor
384                .unwrap_or(CertificateTextAnchor::Middle),
385        }
386    }
387}
388
389#[derive(Debug, Serialize, Deserialize, PartialEq, Eq, Clone, Copy)]
390struct DatabaseCertificateConfigurationInner<'a> {
391    pub id: Uuid,
392    pub certificate_owner_name_y_pos: &'a str,
393    pub certificate_owner_name_x_pos: &'a str,
394    pub certificate_owner_name_font_size: &'a str,
395    pub certificate_owner_name_text_color: &'a str,
396    pub certificate_owner_name_text_anchor: CertificateTextAnchor,
397    pub certificate_validate_url_y_pos: &'a str,
398    pub certificate_validate_url_x_pos: &'a str,
399    pub certificate_validate_url_font_size: &'a str,
400    pub certificate_validate_url_text_color: &'a str,
401    pub certificate_validate_url_text_anchor: CertificateTextAnchor,
402    pub certificate_date_y_pos: &'a str,
403    pub certificate_date_x_pos: &'a str,
404    pub certificate_date_font_size: &'a str,
405    pub certificate_date_text_color: &'a str,
406    pub certificate_date_text_anchor: CertificateTextAnchor,
407    pub certificate_locale: &'a str,
408    pub paper_size: PaperSize,
409    pub background_svg_path: &'a str,
410    pub background_svg_file_upload_id: Uuid,
411    pub overlay_svg_path: Option<&'a str>,
412    pub overlay_svg_file_upload_id: Option<Uuid>,
413    pub render_certificate_grade: bool,
414    pub certificate_grade_y_pos: &'a str,
415    pub certificate_grade_x_pos: &'a str,
416    pub certificate_grade_font_size: &'a str,
417    pub certificate_grade_text_color: &'a str,
418    pub certificate_grade_text_anchor: CertificateTextAnchor,
419}
420
421pub async fn insert(
422    conn: &mut PgConnection,
423    conf: &DatabaseCertificateConfiguration,
424) -> ModelResult<CertificateConfiguration> {
425    let conf = conf.build();
426    let configuration = sqlx::query_as!(
427        CertificateConfiguration,
428        r#"
429INSERT INTO public.certificate_configurations (
430    certificate_owner_name_y_pos,
431    certificate_owner_name_x_pos,
432    certificate_owner_name_font_size,
433    certificate_owner_name_text_color,
434    certificate_owner_name_text_anchor,
435    certificate_validate_url_y_pos,
436    certificate_validate_url_x_pos,
437    certificate_validate_url_font_size,
438    certificate_validate_url_text_color,
439    certificate_validate_url_text_anchor,
440    certificate_date_y_pos,
441    certificate_date_x_pos,
442    certificate_date_font_size,
443    certificate_date_text_color,
444    certificate_date_text_anchor,
445    certificate_locale,
446    paper_size,
447    background_svg_path,
448    background_svg_file_upload_id,
449    overlay_svg_path,
450    overlay_svg_file_upload_id,
451    render_certificate_grade,
452    certificate_grade_y_pos,
453    certificate_grade_x_pos,
454    certificate_grade_font_size,
455    certificate_grade_text_color,
456    certificate_grade_text_anchor
457  )
458VALUES (
459    $1,
460    $2,
461    $3,
462    $4,
463    $5,
464    $6,
465    $7,
466    $8,
467    $9,
468    $10,
469    $11,
470    $12,
471    $13,
472    $14,
473    $15,
474    $16,
475    $17,
476    $18,
477    $19,
478    $20,
479    $21,
480    $22,
481    $23,
482    $24,
483    $25,
484    $26,
485    $27
486  )
487RETURNING id,
488  created_at,
489  updated_at,
490  deleted_at,
491  certificate_owner_name_y_pos,
492  certificate_owner_name_x_pos,
493  certificate_owner_name_font_size,
494  certificate_owner_name_text_color,
495  certificate_owner_name_text_anchor as "certificate_owner_name_text_anchor: _",
496  certificate_validate_url_y_pos,
497  certificate_validate_url_x_pos,
498  certificate_validate_url_font_size,
499  certificate_validate_url_text_color,
500  certificate_validate_url_text_anchor as "certificate_validate_url_text_anchor: _",
501  certificate_date_y_pos,
502  certificate_date_x_pos,
503  certificate_date_font_size,
504  certificate_date_text_color,
505  certificate_date_text_anchor as "certificate_date_text_anchor: _",
506  certificate_locale,
507  paper_size as "paper_size: _",
508  background_svg_path,
509  background_svg_file_upload_id,
510  overlay_svg_path,
511  overlay_svg_file_upload_id,
512  render_certificate_grade,
513  certificate_grade_y_pos,
514  certificate_grade_x_pos,
515  certificate_grade_font_size,
516  certificate_grade_text_color,
517  certificate_grade_text_anchor as "certificate_grade_text_anchor: _"
518"#,
519        conf.certificate_owner_name_y_pos,
520        conf.certificate_owner_name_x_pos,
521        conf.certificate_owner_name_font_size,
522        conf.certificate_owner_name_text_color,
523        conf.certificate_owner_name_text_anchor as CertificateTextAnchor,
524        conf.certificate_validate_url_y_pos,
525        conf.certificate_validate_url_x_pos,
526        conf.certificate_validate_url_font_size,
527        conf.certificate_validate_url_text_color,
528        conf.certificate_validate_url_text_anchor as CertificateTextAnchor,
529        conf.certificate_date_y_pos,
530        conf.certificate_date_x_pos,
531        conf.certificate_date_font_size,
532        conf.certificate_date_text_color,
533        conf.certificate_date_text_anchor as CertificateTextAnchor,
534        conf.certificate_locale,
535        conf.paper_size as PaperSize,
536        conf.background_svg_path,
537        conf.background_svg_file_upload_id,
538        conf.overlay_svg_path,
539        conf.overlay_svg_file_upload_id,
540        conf.render_certificate_grade,
541        conf.certificate_grade_y_pos,
542        conf.certificate_grade_x_pos,
543        conf.certificate_grade_font_size,
544        conf.certificate_grade_text_color,
545        conf.certificate_grade_text_anchor as CertificateTextAnchor
546    )
547    .fetch_one(conn)
548    .await?;
549    Ok(configuration)
550}
551
552pub async fn update(
553    conn: &mut PgConnection,
554    id: Uuid,
555    conf: &DatabaseCertificateConfiguration,
556) -> ModelResult<()> {
557    let conf = conf.build();
558    sqlx::query!(
559        r#"
560UPDATE public.certificate_configurations
561SET certificate_owner_name_y_pos = $1,
562  certificate_owner_name_x_pos = $2,
563  certificate_owner_name_font_size = $3,
564  certificate_owner_name_text_color = $4,
565  certificate_owner_name_text_anchor = $5,
566  certificate_validate_url_y_pos = $6,
567  certificate_validate_url_x_pos = $7,
568  certificate_validate_url_font_size = $8,
569  certificate_validate_url_text_color = $9,
570  certificate_validate_url_text_anchor = $10,
571  certificate_date_y_pos = $11,
572  certificate_date_x_pos = $12,
573  certificate_date_font_size = $13,
574  certificate_date_text_color = $14,
575  certificate_date_text_anchor = $15,
576  certificate_locale = $16,
577  paper_size = $17,
578  background_svg_path = $18,
579  background_svg_file_upload_id = $19,
580  overlay_svg_path = $20,
581  overlay_svg_file_upload_id = $21,
582  render_certificate_grade = $22,
583  certificate_grade_y_pos = $23,
584  certificate_grade_x_pos = $24,
585  certificate_grade_font_size = $25,
586  certificate_grade_text_color = $26,
587  certificate_grade_text_anchor = $27
588WHERE id = $28
589"#,
590        conf.certificate_owner_name_y_pos,
591        conf.certificate_owner_name_x_pos,
592        conf.certificate_owner_name_font_size,
593        conf.certificate_owner_name_text_color,
594        conf.certificate_owner_name_text_anchor as CertificateTextAnchor,
595        conf.certificate_validate_url_y_pos,
596        conf.certificate_validate_url_x_pos,
597        conf.certificate_validate_url_font_size,
598        conf.certificate_validate_url_text_color,
599        conf.certificate_validate_url_text_anchor as CertificateTextAnchor,
600        conf.certificate_date_y_pos,
601        conf.certificate_date_x_pos,
602        conf.certificate_date_font_size,
603        conf.certificate_date_text_color,
604        conf.certificate_date_text_anchor as CertificateTextAnchor,
605        conf.certificate_locale,
606        conf.paper_size as PaperSize,
607        conf.background_svg_path,
608        conf.background_svg_file_upload_id,
609        conf.overlay_svg_path,
610        conf.overlay_svg_file_upload_id,
611        conf.render_certificate_grade,
612        conf.certificate_grade_y_pos,
613        conf.certificate_grade_x_pos,
614        conf.certificate_grade_font_size,
615        conf.certificate_grade_text_color,
616        conf.certificate_grade_text_anchor as CertificateTextAnchor,
617        id
618    )
619    .execute(conn)
620    .await?;
621    Ok(())
622}
623
624pub async fn delete(conn: &mut PgConnection, id: Uuid) -> ModelResult<()> {
625    sqlx::query!(
626        "
627UPDATE certificate_configurations
628SET deleted_at = now()
629WHERE id = $1
630",
631        id
632    )
633    .execute(conn)
634    .await?;
635    Ok(())
636}