veloren_server/settings/admin.rs
1//! Versioned admins settings files.
2
3// NOTE: Needed to allow the second-to-last migration to call try_into().
4
5use super::{ADMINS_FILENAME as FILENAME, MIGRATION_UPGRADE_GUARANTEE};
6use crate::settings::editable::{EditableSetting, Version};
7use core::convert::{Infallible, TryFrom, TryInto};
8use serde::{Deserialize, Serialize};
9
10/// NOTE: Always replace this with the latest admins version. Then update the
11/// AdminsRaw, the TryFrom<AdminsRaw> for Admins, the previously most recent
12/// module, and add a new module for the latest version! Please respect the
13/// migration upgrade guarantee found in the parent module with any upgrade.
14pub use self::v1::*;
15
16/// Versioned settings files, one per version (v0 is only here as an example; we
17/// do not expect to see any actual v0 settings files).
18#[derive(Deserialize, Serialize)]
19pub enum AdminsRaw {
20 V0(v0::Admins),
21 V1(Admins),
22}
23
24impl From<Admins> for AdminsRaw {
25 fn from(value: Admins) -> Self {
26 // Replace variant with that of current latest version.
27 Self::V1(value)
28 }
29}
30
31impl TryFrom<AdminsRaw> for (Version, Admins) {
32 type Error = <Admins as EditableSetting>::Error;
33
34 fn try_from(value: AdminsRaw) -> Result<Self, <Admins as EditableSetting>::Error> {
35 use AdminsRaw::*;
36 Ok(match value {
37 // Old versions
38 V0(value) => (Version::Old, value.try_into()?),
39 // Latest version (move to old section using the pattern of other old version when it
40 // is no longer latest).
41 V1(mut value) => (value.validate()?, value),
42 })
43 }
44}
45
46type Final = Admins;
47
48impl EditableSetting for Admins {
49 type Error = Infallible;
50 type Legacy = legacy::Admins;
51 type Setting = AdminsRaw;
52
53 const FILENAME: &'static str = FILENAME;
54}
55
56mod legacy {
57 use super::{Final, MIGRATION_UPGRADE_GUARANTEE, v0 as next};
58 use authc::Uuid;
59 use core::convert::TryInto;
60 use hashbrown::HashSet;
61 use serde::{Deserialize, Serialize};
62
63 #[derive(Deserialize, Serialize, Default)]
64 #[serde(transparent)]
65 pub struct Admins(pub(super) HashSet<Uuid>);
66
67 impl From<Admins> for Final {
68 /// Legacy migrations can be migrated to the latest version through the
69 /// process of "chaining" migrations, starting from
70 /// `next::Admins`.
71 ///
72 /// Note that legacy files are always valid, which is why we implement
73 /// From rather than TryFrom.
74 fn from(value: Admins) -> Self {
75 next::Admins::migrate(value)
76 .try_into()
77 .expect(MIGRATION_UPGRADE_GUARANTEE)
78 }
79 }
80}
81
82/// This module represents a admins version that isn't actually used. It is
83/// here and part of the migration process to provide an example for how to
84/// perform a migration for an old version; please use this as a reference when
85/// constructing new migrations.
86mod v0 {
87 use super::{Final, MIGRATION_UPGRADE_GUARANTEE, legacy as prev, v1 as next};
88 use crate::settings::editable::{EditableSetting, Version};
89 use authc::Uuid;
90 use core::convert::{TryFrom, TryInto};
91 use hashbrown::HashSet;
92 use serde::{Deserialize, Serialize};
93
94 #[derive(Clone, Deserialize, Serialize, Default)]
95 #[serde(transparent)]
96 pub struct Admins(pub(super) HashSet<Uuid>);
97
98 impl Admins {
99 /// One-off migration from the previous version. This must be
100 /// guaranteed to produce a valid settings file as long as it is
101 /// called with a valid settings file from the previous version.
102 pub(super) fn migrate(prev: prev::Admins) -> Self { Admins(prev.0) }
103
104 /// Perform any needed validation on this admins that can't be done
105 /// using parsing.
106 ///
107 /// The returned version being "Old" indicates the loaded setting has
108 /// been modified during validation (this is why validate takes
109 /// `&mut self`).
110 pub(super) fn validate(&mut self) -> Result<Version, <Final as EditableSetting>::Error> {
111 Ok(Version::Latest)
112 }
113 }
114
115 /// Pretty much every TryFrom implementation except that of the very last
116 /// version should look exactly like this.
117 impl TryFrom<Admins> for Final {
118 type Error = <Final as EditableSetting>::Error;
119
120 #[expect(clippy::useless_conversion)]
121 fn try_from(mut value: Admins) -> Result<Final, Self::Error> {
122 value.validate()?;
123 Ok(next::Admins::migrate(value)
124 .try_into()
125 .expect(MIGRATION_UPGRADE_GUARANTEE))
126 }
127 }
128}
129
130mod v1 {
131 use super::{Final, v0 as prev};
132 use crate::settings::editable::{EditableSetting, Version};
133 use authc::Uuid;
134 use chrono::{Utc, prelude::*};
135 use common::comp::AdminRole;
136 use core::ops::{Deref, DerefMut};
137 use hashbrown::HashMap;
138 use serde::{Deserialize, Serialize};
139 /* use super::v2 as next; */
140
141 /// Important: even if the role we are storing here appears to be identical
142 /// to one used in another versioned store (like banlist::Role), we
143 /// *must* have our own versioned copy! This ensures that if there's an
144 /// update to the role somewhere else, the conversion function between
145 /// them will break, letting people make an intelligent decision.
146 ///
147 /// In particular, *never remove variants from this enum* (or any other enum
148 /// in a versioned settings file) without bumping the version and
149 /// writing a migration that understands how to properly deal with
150 /// existing instances of the old variant (you can delete From instances
151 /// for the old variants at this point). Otherwise, we will lose
152 /// compatibility with old settings files, since we won't be able to
153 /// deserialize them!
154 #[derive(Clone, Copy, Debug, Deserialize, Eq, Ord, PartialOrd, PartialEq, Serialize)]
155 pub enum Role {
156 Moderator = 0,
157 Admin = 1,
158 }
159
160 impl From<AdminRole> for Role {
161 fn from(value: AdminRole) -> Self {
162 match value {
163 AdminRole::Moderator => Self::Moderator,
164 AdminRole::Admin => Self::Admin,
165 }
166 }
167 }
168
169 impl From<Role> for AdminRole {
170 fn from(value: Role) -> Self {
171 match value {
172 Role::Moderator => Self::Moderator,
173 Role::Admin => Self::Admin,
174 }
175 }
176 }
177
178 #[derive(Clone, Deserialize, Serialize)]
179 /// NOTE: This does not include info structs like other settings, because we
180 /// (deliberately) provide no interface for creating new mods or admins
181 /// except through the command line, ensuring that the host of the
182 /// server has total control over these things and avoiding the creation
183 /// of code paths to alter the admin list that are accessible during normal
184 /// gameplay.
185 pub struct AdminRecord {
186 /// NOTE: Should only be None for migrations from legacy data.
187 pub username_when_admined: Option<String>,
188 /// Date that the user was given this role.
189 pub date: DateTime<Utc>,
190 pub role: Role,
191 }
192
193 #[derive(Clone, Deserialize, Serialize, Default)]
194 #[serde(transparent)]
195 /// NOTE: Records should only be unavailable for cases where we are
196 /// migration from a legacy version.
197 pub struct Admins(pub(super) HashMap<Uuid, AdminRecord>);
198
199 impl Deref for Admins {
200 type Target = HashMap<Uuid, AdminRecord>;
201
202 fn deref(&self) -> &Self::Target { &self.0 }
203 }
204
205 impl DerefMut for Admins {
206 fn deref_mut(&mut self) -> &mut Self::Target { &mut self.0 }
207 }
208
209 impl Admins {
210 /// One-off migration from the previous version. This must be
211 /// guaranteed to produce a valid settings file as long as it is
212 /// called with a valid settings file from the previous version.
213 pub(super) fn migrate(prev: prev::Admins) -> Self {
214 // The role assignment date for migrations from legacy is the current one; we
215 // could record that they actually have an unknown start date, but
216 // this would just complicate the format.
217 let date = Utc::now();
218 Admins(
219 prev.0
220 .into_iter()
221 .map(|uid| {
222 (uid, AdminRecord {
223 date,
224 // We don't have username information for old admin records.
225 username_when_admined: None,
226 // All legacy roles are Admin, because we didn't have any other roles at
227 // the time.
228 role: Role::Admin,
229 })
230 })
231 .collect(),
232 )
233 }
234
235 /// Perform any needed validation on this admins that can't be done
236 /// using parsing.
237 ///
238 /// The returned version being "Old" indicates the loaded setting has
239 /// been modified during validation (this is why validate takes
240 /// `&mut self`).
241 pub(super) fn validate(&mut self) -> Result<Version, <Final as EditableSetting>::Error> {
242 Ok(Version::Latest)
243 }
244 }
245
246 // NOTE: Whenever there is a version upgrade, copy this note as well as the
247 // commented-out code below to the next version, then uncomment the code
248 // for this version.
249 /* impl TryFrom<Admins> for Final {
250 type Error = <Final as EditableSetting>::Error;
251
252 fn try_from(mut value: Admins) -> Result<Final, Self::Error> {
253 value.validate()?;
254 Ok(next::Admins::migrate(value).try_into().expect(MIGRATION_UPGRADE_GUARANTEE))
255 }
256 } */
257}