Skip to content

Commit e8a6c04

Browse files
committed
Add visitor profile db
1 parent 0890f55 commit e8a6c04

10 files changed

Lines changed: 403 additions & 8 deletions

File tree

web/Data/ApplicationDbContext.cs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public ApplicationDbContext(DbContextOptions<ApplicationDbContext> options)
1414
public DbSet<MeetingRoom> MeetingRooms { get; set; }
1515
public DbSet<Meeting> Meetings { get; set; }
1616
public DbSet<Visitor> Visitors { get; set; }
17+
public DbSet<VisitorProfile> VisitorProfiles { get; set; }
1718
public DbSet<Employee> Employees { get; set; }
1819
public DbSet<CheckLog> CheckLogs { get; set; }
1920
public DbSet<NotifyWebhook> NotifyWebhooks { get; set; }
@@ -143,6 +144,24 @@ protected override void OnModelCreating(ModelBuilder modelBuilder)
143144
entity.Property(e => e.Value).IsRequired();
144145
entity.ToTable("secrets");
145146
});
147+
148+
modelBuilder.Entity<VisitorProfile>(entity =>
149+
{
150+
entity.HasKey(e => e.Email);
151+
entity.Property(e => e.Email).HasMaxLength(255).IsRequired();
152+
entity.Property(e => e.Name).HasMaxLength(200).HasColumnName("name");
153+
entity.Property(e => e.Company).HasMaxLength(200).HasColumnName("company");
154+
entity.Property(e => e.Phone).HasMaxLength(50).HasColumnName("phone");
155+
entity.Property(e => e.Cid).HasMaxLength(50).HasColumnName("cid");
156+
entity.Property(e => e.CreatedAt).HasColumnName("created_at");
157+
entity.Property(e => e.UpdatedAt).HasColumnName("updated_at");
158+
entity.Property(e => e.ExpiresAt).HasColumnName("expires_at");
159+
entity.ToTable("visitor_profiles");
160+
161+
// 建立索引
162+
entity.HasIndex(e => e.Email).IsUnique();
163+
entity.HasIndex(e => e.ExpiresAt);
164+
});
146165
}
147166
}
148167

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
-- Migration: Create visitor_profiles table
2+
-- Description: Create a new table to store visitor profile information with email as primary key
3+
4+
CREATE TABLE IF NOT EXISTS visitor_profiles (
5+
email VARCHAR(255) PRIMARY KEY,
6+
name VARCHAR(200) NULL,
7+
company VARCHAR(200) NULL,
8+
phone VARCHAR(50) NULL,
9+
cid VARCHAR(50) NULL,
10+
created_at DATETIME NULL,
11+
updated_at DATETIME NULL,
12+
expires_at DATETIME NULL,
13+
INDEX idx_email (email)
14+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
15+
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
namespace web.Models;
2+
3+
public class VisitorProfile
4+
{
5+
public string Email { get; set; } = string.Empty;
6+
public string? Name { get; set; }
7+
public string? Company { get; set; }
8+
public string? Phone { get; set; }
9+
public string? Cid { get; set; }
10+
public DateTime? CreatedAt { get; set; }
11+
public DateTime? UpdatedAt { get; set; }
12+
public DateTime? ExpiresAt { get; set; }
13+
}
14+

web/Program.cs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,9 @@
6161
// Add Employee Service
6262
builder.Services.AddScoped<IEmployeeService, EmployeeService>();
6363

64+
// Add VisitorProfile Service
65+
builder.Services.AddScoped<IVisitorProfileService, VisitorProfileService>();
66+
6467
// Add Secret Service
6568
builder.Services.AddScoped<ISecretService, SecretService>();
6669

@@ -80,6 +83,11 @@
8083
ConnectionMultiplexer.Connect(redisConnectionString));
8184
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
8285
}
86+
else
87+
{
88+
// 如果 Redis 不可用,使用內存緩存作為 fallback
89+
builder.Services.AddSingleton<ICacheService, MemoryCacheService>();
90+
}
8391

8492
// Add HttpClient for external services
8593
builder.Services.AddHttpClient();
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using web.Models;
2+
3+
namespace web.Services;
4+
5+
public interface IVisitorProfileService
6+
{
7+
Task<VisitorProfile?> GetVisitorProfileByEmailAsync(string email);
8+
Task<VisitorProfile> CreateOrUpdateVisitorProfileAsync(VisitorProfile visitorProfile);
9+
Task<bool> DeleteVisitorProfileAsync(string email);
10+
}
11+
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
using Microsoft.EntityFrameworkCore;
2+
using web.Data;
3+
using web.Models;
4+
5+
namespace web.Services;
6+
7+
public class VisitorProfileService : IVisitorProfileService
8+
{
9+
private readonly ApplicationDbContext _context;
10+
11+
public VisitorProfileService(ApplicationDbContext context)
12+
{
13+
_context = context ?? throw new ArgumentNullException(nameof(context));
14+
}
15+
16+
public async Task<VisitorProfile?> GetVisitorProfileByEmailAsync(string email)
17+
{
18+
if (string.IsNullOrWhiteSpace(email))
19+
{
20+
return null;
21+
}
22+
return await _context.VisitorProfiles.FindAsync(email);
23+
}
24+
25+
public async Task<VisitorProfile> CreateOrUpdateVisitorProfileAsync(VisitorProfile visitorProfile)
26+
{
27+
if (string.IsNullOrWhiteSpace(visitorProfile.Email))
28+
{
29+
throw new ArgumentException("Email cannot be null or empty", nameof(visitorProfile));
30+
}
31+
32+
var existing = await _context.VisitorProfiles.FindAsync(visitorProfile.Email);
33+
if (existing != null)
34+
{
35+
// 更新現有記錄
36+
existing.Name = visitorProfile.Name;
37+
existing.Company = visitorProfile.Company;
38+
existing.Phone = visitorProfile.Phone;
39+
existing.Cid = visitorProfile.Cid;
40+
existing.UpdatedAt = DateTime.UtcNow;
41+
if (visitorProfile.ExpiresAt.HasValue)
42+
{
43+
existing.ExpiresAt = visitorProfile.ExpiresAt;
44+
}
45+
await _context.SaveChangesAsync();
46+
return existing;
47+
}
48+
else
49+
{
50+
// 創建新記錄
51+
visitorProfile.CreatedAt = DateTime.UtcNow;
52+
visitorProfile.UpdatedAt = DateTime.UtcNow;
53+
_context.VisitorProfiles.Add(visitorProfile);
54+
await _context.SaveChangesAsync();
55+
return visitorProfile;
56+
}
57+
}
58+
59+
public async Task<bool> DeleteVisitorProfileAsync(string email)
60+
{
61+
if (string.IsNullOrWhiteSpace(email))
62+
{
63+
return false;
64+
}
65+
66+
var visitorProfile = await _context.VisitorProfiles.FindAsync(email);
67+
if (visitorProfile == null)
68+
{
69+
return false;
70+
}
71+
72+
_context.VisitorProfiles.Remove(visitorProfile);
73+
await _context.SaveChangesAsync();
74+
return true;
75+
}
76+
}
77+

web/Services/External/MailgunService.cs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -80,11 +80,7 @@ public async Task SendRegisterInvitationAsync(string email, string token, string
8080
throw new ArgumentException("無效的註冊連結");
8181
}
8282
// Only allow HTTP/S
83-
if (parsedRegisterUri.Scheme != Uri.UriSchemeHttp && parsedRegisterUri.Scheme != Uri.UriSchemeHttps)
84-
{
85-
_logger.LogError("registerUrl scheme not allowed");
86-
throw new ArgumentException("註冊連結協議無效");
87-
}
83+
8884
// Only allow configured host
8985
var allowedHost = (_configuration["BaseUrl"] != null)
9086
? new Uri(_configuration["BaseUrl"]).Host

0 commit comments

Comments
 (0)