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}