using System.ComponentModel.DataAnnotations; using EARSystem.Core; namespace EARSystem.Web.Models; public class LoginInput { [Required, EmailAddress, StringLength(254)] public string Email { get; set; } = ""; [Required, StringLength(128)] public string Password { get; set; } = ""; public bool Remember { get; set; } public string? ReturnUrl { get; set; } } public class RegisterInput { [Required, StringLength(40, MinimumLength = 1)] public string DisplayName { get; set; } = ""; [Required, EmailAddress, StringLength(254)] public string Email { get; set; } = ""; [Required, StringLength(128, MinimumLength = 10)] public string Password { get; set; } = ""; [Compare(nameof(Password))] public string ConfirmPassword { get; set; } = ""; } public class MoodInput : IValidatableObject { [Range(1, 5)] public int Mood { get; set; } = 3; [Range(1, 5)] public int Energy { get; set; } = 3; [Range(1, 5)] public int Stress { get; set; } = 3; [StringLength(5000)] public string Thoughts { get; set; } = ""; public string[] Tags { get; set; } = []; public DateTimeOffset RecordedAt { get; set; } public Guid? Version { get; set; } public IEnumerable Validate(ValidationContext context) { if (RecordedAt == default || RecordedAt > DateTimeOffset.UtcNow.AddMinutes(1)) yield return new("请选择有效的记录时间,不能记录未来状态。", [nameof(RecordedAt)]); if (Tags.Length > 12 || Tags.Except(Labels.EmotionTags.Concat(Labels.SceneTags)).Any()) yield return new("请选择支持的情绪或场景标签。", [nameof(Tags)]); } } public class TagInput { [StringLength(5000)] public string Text { get; set; } = ""; } public class RecommendationInput : IValidatableObject { [Range(-180, 180)] public double Longitude { get; set; } [Range(-90, 90)] public double Latitude { get; set; } public string CoordinateSystem { get; set; } = "GCJ02"; public int RadiusMeters { get; set; } = 3000; [Range(1, 100000)] public decimal? Budget { get; set; } public PlaceCategory[] Categories { get; set; } = []; public bool OpenOnly { get; set; } public RecommendationGoal? Goal { get; set; } public DataMode Mode { get; set; } = DataMode.Demo; public IEnumerable Validate(ValidationContext context) { if (!double.IsFinite(Longitude) || !double.IsFinite(Latitude)) yield return new("坐标无效。"); if (CoordinateSystem is not ("GCJ02" or "WGS84")) yield return new("坐标系无效。"); if (!new[] { 1000, 3000, 5000, 10000 }.Contains(RadiusMeters)) yield return new("距离范围无效。"); if (!Enum.IsDefined(Mode) || Goal.HasValue && !Enum.IsDefined(Goal.Value) || Categories.Length > 6 || Categories.Any(x => !Enum.IsDefined(x))) yield return new("推荐选项无效。"); } public RecommendationQuery Query() => new() { Location = new(Longitude, Latitude, CoordinateSystem), RadiusMeters = RadiusMeters, Budget = Budget, Categories = Categories, OpenOnly = OpenOnly, Goal = Goal, Mode = Mode }; } public class ExperienceInput : IValidatableObject { public Guid PlaceId { get; set; } public DateTimeOffset VisitedAt { get; set; } [Range(1, 5)] public int Satisfaction { get; set; } = 4; [Range(1, 5)] public int? MoodBefore { get; set; } [Range(1, 5)] public int? MoodAfter { get; set; } [Range(1, 5)] public int Quiet { get; set; } = 3; [Range(1, 5)] public int Comfort { get; set; } = 3; [Range(1, 5)] public int Nature { get; set; } = 3; [Range(1, 5)] public int Liveliness { get; set; } = 3; [Range(1, 5)] public int Social { get; set; } = 3; [Range(1, 5)] public int Focus { get; set; } = 3; [StringLength(5000)] public string Thoughts { get; set; } = ""; public bool Contribute { get; set; } public bool SyncDiary { get; set; } [Range(1, 5)] public int Energy { get; set; } = 3; [Range(1, 5)] public int Stress { get; set; } = 3; public Guid? Version { get; set; } public IEnumerable Validate(ValidationContext context) { if (PlaceId == Guid.Empty || VisitedAt == default || VisitedAt > DateTimeOffset.UtcNow.AddMinutes(1)) yield return new("请选择地点与有效的到访时间。"); if (SyncDiary && !MoodAfter.HasValue) yield return new("同步日记时请填写体验后的心情。"); } public Experience Entity(Guid? id = null) => new() { Id = id ?? Guid.NewGuid(), PlaceId = PlaceId, VisitedAt = VisitedAt, Satisfaction = Satisfaction, MoodBefore = MoodBefore, MoodAfter = MoodAfter, Quiet = Quiet, Comfort = Comfort, Nature = Nature, Liveliness = Liveliness, Social = Social, Focus = Focus, Thoughts = Thoughts, Contribute = Contribute }; } public class FavoriteInput { public Guid PlaceId { get; set; } } public class PreferenceInput : IValidatableObject { public PlaceCategory[] FavoriteCategories { get; set; } = []; public PlaceCategory[] BlockedCategories { get; set; } = []; public Guid[] BlockedPlaces { get; set; } = []; [Range(1, 100000)] public decimal? Budget { get; set; } public int RadiusMeters { get; set; } = 3000; public IEnumerable Validate(ValidationContext context) { if (FavoriteCategories.Concat(BlockedCategories).Any(x => !Enum.IsDefined(x)) || FavoriteCategories.Length > 6 || BlockedCategories.Length > 6 || BlockedPlaces.Length > 100) yield return new("偏好选项无效。"); if (!new[] { 1000, 3000, 5000, 10000 }.Contains(RadiusMeters)) yield return new("默认距离无效。"); if (FavoriteCategories.Intersect(BlockedCategories).Any()) yield return new("同一类别不能同时喜欢和屏蔽。"); } }