feat(guildserver): implement guild creation - #55
Conversation
There was a problem hiding this comment.
Pull request overview
Implements end-to-end guild creation in the guildserver: adds a CreateGuild gRPC RPC, persists the guild (with default ranks + leader member) in MySQL with a retry strategy for guild-id allocation collisions, hydrates/updates the in-memory cache, and emits a guild.created event.
Changes:
- Add
CreateGuildRPC + protobuf types, and wire server handlers/middleware. - Implement MySQL
GuildByRealmAndIDloader and transactionalCreateGuild(MAX+1 with duplicate-key retry). - Extend the in-memory cache with a “source of truth” membership lookup and post-create cache hydration; add tests around create/membership behavior.
Reviewed changes
Copilot reviewed 12 out of 16 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| shared/events/producer-guild.go | Add GuildCreated publisher method to the producer interface/implementation. |
| shared/events/mocks/GuildServiceProducer.go | Update event producer mock for GuildCreated. |
| shared/events/events-guild.go | Define GuildEventGuildCreated and its payload + subject mapping. |
| gen/guilds/pb/guilds.pb.go | Regenerated protobuf Go types including CreateGuildParams/Response. |
| gen/guilds/pb/guilds_grpc.pb.go | Regenerated gRPC stubs including CreateGuild. |
| apps/guildserver/service/guilds.go | Implement GuildService.CreateGuild with validation, membership checks, persistence, and event publish. |
| apps/guildserver/service/guilds-create_test.go | Add unit tests for service-level create behavior and validation. |
| apps/guildserver/service/guilds-cache.go | Extend GuildsCache with GuildMembershipSource to bypass stale cached membership. |
| apps/guildserver/service/guilds-cache_inmem.go | Implement source membership read + cache eviction and cache hydration on create. |
| apps/guildserver/service/guilds-cache_inmem_test.go | Add tests for stale membership eviction and leader-online hydration during create. |
| apps/guildserver/server/guilds.go | Add CreateGuild gRPC handler. |
| apps/guildserver/server/guilds-logger_debug.go | Add debug logging middleware for CreateGuild. |
| apps/guildserver/repo/mocks/guilds-repo.go | Update repo mock with CreateGuild. |
| apps/guildserver/repo/guilds.go | Add ErrGuildNameTaken and extend GuildsRepo with CreateGuild. |
| apps/guildserver/repo/guilds_mysql.go | Implement GuildByRealmAndID, add CreateGuild transaction + id-conflict retry, and fix no-rows handling for membership lookup. |
| api/proto/v1/guilds/guilds.proto | Add CreateGuild RPC and message definitions. |
Files not reviewed (4)
- apps/guildserver/repo/mocks/guilds-repo.go: Generated file
- gen/guilds/pb/guilds.pb.go: Generated file
- gen/guilds/pb/guilds_grpc.pb.go: Generated file
- shared/events/mocks/GuildServiceProducer.go: Generated file
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| g.cacheMutex.Lock() | ||
| g.cache[realmID][id] = guild | ||
| for _, member := range guild.GuildMembers { | ||
| if member.PlayerGUID == leaderGUID { | ||
| // Guild creation is always driven by a live session of the leader, | ||
| // but the world may not have flushed the online flag to the | ||
| // characters table yet, so the hydration can miss it. | ||
| member.Status = repo.GuildMemberStatusOnline | ||
| } | ||
| g.guildMembersCache[realmID][member.PlayerGUID] = member | ||
| } | ||
| g.cacheMutex.Unlock() |
There was a problem hiding this comment.
Good catch — added an init guard for the per-realm inner maps before the cache writes.
a479ae0 to
bf0719b
Compare
|
Updated for the gateway-side design: |
218506b to
6bf62e9
Compare
Adds a CreateGuild RPC: the guild row, the five default 3.3.5 ranks and the
leader as guild master are inserted in one transaction, the guild id being
allocated as MAX(guildid)+1 with a retry on primary key conflicts (a
worldserver can still create guilds in-process concurrently). The created
guild is cached hydrated and a guild.created event is published.
Also implements the GuildByRealmAndID mysql repo method that was left as
panic("implement me") - the cache needs it to hydrate the new guild.
(cherry picked from commit 3608d5a3f14348c31ca54faafb07f27948919bfc)
…ild creation The world can disband guilds without going through the guild service (e.g. GM .guild delete handled in-process), leaving a stale positive membership in the in-memory cache. CreateGuild now rechecks a positive answer against the database and evicts the stale cache entry, instead of rejecting with 'already in guild' until the service restarts. Also treats a missing guild_member row as 'not in a guild' in the MySQL repo instead of surfacing sql.ErrNoRows. (cherry picked from commit 2c779169db1722cb20bfa58faf8674a482af09f1)
Guild creation is always driven by a live session of the leader, but in cluster mode the world doesn't flush the online flag to the characters table, so the hydration read the leader back as offline. This left the fresh guild with an empty online-members list: the roster showed the leader disconnected and guild events (e.g. MOTD updates) were broadcast to nobody until the leader relogged. (cherry picked from commit 7000ccaa6c9f19ce89c1f3884f355243a74628d9)
…ity) The core's CreateDefaultGuildRanks gives both Guild Master and Officer GR_RIGHT_ALL; the service only gave the Officer chat rights, so a freshly promoted officer had every guild action greyed out client-side (invite, promote, etc.). Match the core layout and assert it in the create test.
- CreateGuildParams gains signatoryGUIDs; members are inserted with the lowest rank in the same transaction, skipping characters that joined another guild since signing. - guild.created event now carries the added member guids so worldservers can populate the state for the new guild. - New worldserver RPC CanTurnInGuildPetition for the gateway to validate petitions on the worldserver side before calling CreateGuild.
6bf62e9 to
5f8b1ea
Compare
|
Superseded by #81, which regroups the guild creation work into a single PR as discussed. Same content, rebased on current master, with the review points from this PR already applied. |
The guildserver
CreateGuildRPC and the mysqlGuildByRealmAndIDreader were panic stubs. I implement guild creation through the service: it creates the guild, its default ranks, the leader member and the petition signatories (as Initiates, skipping anyone who joined another guild meanwhile) in one transaction, allocates the guild id centrally (MAX+1, retried on the 1062 duplicate-key race so concurrent shards can't collide), hydrates the cache and publishes aguild.createdevent carrying the member GUIDs.GuildByRealmAndIDis now backed by a real query (guild + ranks + members) that the cache reads. Tests cover creation, the id-conflict retry, the signatory handling and the cache hydration.The caller is the gateway on petition turn-in (#58), per the design we settled on Discord.