1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
// ┃ Copyright: (c) 2023, Mike 'PhiSyX' S. (https://github.com/PhiSyX)         ┃
// ┃ SPDX-License-Identifier: MPL-2.0                                          ┃
// ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃
// ┃                                                                           ┃
// ┃  This Source Code Form is subject to the terms of the Mozilla Public      ┃
// ┃  License, v. 2.0. If a copy of the MPL was not distributed with this      ┃
// ┃  file, You can obtain one at https://mozilla.org/MPL/2.0/.                ┃
// ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛

use lexa_database::SGBD;

// --------- //
// Interface //
// --------- //

#[cfg(feature = "database-postgres")]
#[async_trait::async_trait]
pub trait ModelInterface {
	/// Entité du modèle.
	type Entity: sqlb::HasFields
		+ serde::Serialize
		+ serde::de::DeserializeOwned
		+ Sync
		+ Send;

	/// Crée une instance du [model](Self).
	fn new(database: &super::services::postgres::PostgresService) -> Self
	where
		Self: Sized;

	fn database(&self) -> &super::services::postgres::PostgresService;

	/// Nom de la table du model, correspondant à un nom de table d'une base de
	/// données.
	fn table() -> &'static str;

	/// Tous les champs d'une entité.
	fn fields() -> &'static [&'static str] {
		<Self::Entity as sqlb::HasFields>::field_names()
	}

	/// Récupère tous les résultats de la table [Self::Entity::table()].
	async fn fetch_all<'c>(&self) -> Result<Vec<Self::Entity>, Error>
	where
		for<'r> <Self as ModelInterface>::Entity:
			sqlx::FromRow<'r, sqlx::postgres::PgRow>,
		<Self as ModelInterface>::Entity: Unpin,
	{
		let records: Vec<Self::Entity> = sqlb::select()
			.columns(Self::fields())
			.table(Self::table())
			.fetch_all(self.database().pool())
			.await?;
		Ok(records)
	}

	/// Récupère tous les résultats de la table [Self::Entity::table()] en
	/// fonction d'un ID.
	async fn fetch_all_by_id(
		&self,
		id: impl ToString + Send + Sync,
	) -> Result<Vec<Self::Entity>, Error>
	where
		for<'r> <Self as ModelInterface>::Entity:
			sqlx::FromRow<'r, sqlx::postgres::PgRow>,
		<Self as ModelInterface>::Entity: Unpin,
	{
		let records: Vec<Self::Entity> = sqlb::select()
			.columns(Self::fields())
			.table(Self::table())
			.and_where_eq("id", id.to_string())
			.fetch_all(self.database().pool())
			.await?;
		Ok(records)
	}

	/// Cherche dans la table de la base de donnée s'il existe l'ID passé en
	/// argument. En supposant qu'il existe un champ `id` dans la table.
	async fn find_by_id(
		&self,
		id: impl ToString + Send + Sync,
	) -> Result<Self::Entity, Error>
	where
		for<'r> <Self as ModelInterface>::Entity:
			sqlx::FromRow<'r, sqlx::postgres::PgRow>,
		<Self as ModelInterface>::Entity: Unpin,
	{
		let record: Self::Entity = sqlb::select()
			.columns(Self::fields())
			.table(Self::table())
			.and_where_eq("id", id.to_string())
			.limit(1)
			.fetch_one(self.database().pool())
			.await?;
		Ok(record)
	}

	/// Ajoute une entité en base de données.
	async fn create(
		&self,
		data: impl sqlb::HasFields + Send,
	) -> Result<Self::Entity, Error>
	where
		for<'r> <Self as ModelInterface>::Entity:
			sqlx::FromRow<'r, sqlx::postgres::PgRow>,
		<Self as ModelInterface>::Entity: Unpin,
	{
		let record: Self::Entity = sqlb::insert()
			.table(Self::table())
			.data(data.all_fields())
			.returning(Self::fields())
			.fetch_one(self.database().pool())
			.await?;
		Ok(record)
	}

	/// Supprime une entité en base de données en fonction d'un ID.
	async fn delete_by_id(
		&self,
		id: impl ToString + Send + Sync,
	) -> Result<(), Error> {
		sqlb::delete()
			.table(Self::table())
			.and_where_eq("id", id.to_string())
			.exec(self.database().pool())
			.await?;
		Ok(())
	}

	/// Modifie les champs d'une table en base de données.
	async fn update_by_id(
		&self,
		id: impl ToString + Send + Sync,
		data: impl sqlb::HasFields + Send,
	) -> Result<Self::Entity, Error>
	where
		for<'r> <Self as ModelInterface>::Entity:
			sqlx::FromRow<'r, sqlx::postgres::PgRow>,
		<Self as ModelInterface>::Entity: Unpin,
	{
		let record: Self::Entity = sqlb::update()
			.table(Self::table())
			.data(data.all_fields())
			.and_where_eq("id", id.to_string())
			.returning(Self::fields())
			.fetch_one(self.database().pool())
			.await?;
		Ok(record)
	}
}

#[cfg(not(feature = "database-postgres"))]
pub trait ModelInterface {
	/// Entité du modèle.
	type Entity: serde::Serialize + serde::de::DeserializeOwned + Sync + Send;
	type Database;

	/// Crée une instance du [model](Self).
	fn new(database: Self::Database) -> Self
	where
		Self: Sized;

	fn database(&self) -> &Self::Database;

	/// Nom de la table du model, correspondant à un nom de table d'une base de
	/// données.
	fn table() -> &'static str;
}

// ----------- //
// Énumération //
// ----------- //

#[derive(Debug)]
#[derive(thiserror::Error)]
pub enum Error {
	#[error("{0}")]
	Sqlx(#[from] sqlx::Error),
}