|
4 | 4 | using FruityFoundation.Base.Extensions; |
5 | 5 | using FruityFoundation.Base.Structures; |
6 | 6 | using Npgsql; |
| 7 | +using ResultMonad; |
7 | 8 |
|
8 | 9 | namespace ApplicationData.Services; |
9 | 10 |
|
@@ -92,29 +93,48 @@ public async Task DeleteLinkById(int linkId) |
92 | 93 | await tx.CommitAsync(); |
93 | 94 | } |
94 | 95 |
|
95 | | - public async Task<int> CreateLink(NewLink link) |
| 96 | + public abstract record CreateLinkError |
96 | 97 | { |
97 | | - await using var tx = await _db.CreateTransactionAsync(); |
98 | | - var linkIdResult = (int?)await tx.ExecuteSqlScalarAsync( |
99 | | - @"INSERT INTO app.links (reddit_post_id, link_url, link_type, created_at, is_deleted, owner) |
100 | | - VALUES (@redditPostId, @linkUrl, @linkType, NOW(), false, @userId) |
101 | | - RETURNING link_id", |
102 | | - new NpgsqlParameter[] |
103 | | - { |
104 | | - new("@redditPostId", link.RedditPostId), |
105 | | - new("@linkUrl", link.LinkUrl), |
106 | | - new("@linkType", link.LinkType.RawValue), |
107 | | - new("@userId", link.OwnerUserId) |
108 | | - }); |
109 | | - |
110 | | - if (!linkIdResult.ToMaybe().Try(out var linkId)) |
111 | | - throw new ApplicationException("Failed to create link"); |
112 | | - |
113 | | - await QueueRedditPostIdForUpdate(tx, link.RedditPostId); |
| 98 | + public T Switch<T>(Func<T> linkAlreadyExists) => this switch |
| 99 | + { |
| 100 | + LinkAlreadyExists => linkAlreadyExists(), |
| 101 | + _ => throw new NotImplementedException($"Type not implemented: {GetType().FullName}") |
| 102 | + }; |
| 103 | + } |
114 | 104 |
|
115 | | - await tx.CommitAsync(); |
| 105 | + public record LinkAlreadyExists : CreateLinkError; |
116 | 106 |
|
117 | | - return linkId; |
| 107 | + public async Task<Result<int, CreateLinkError>> CreateLink(NewLink link) |
| 108 | + { |
| 109 | + try |
| 110 | + { |
| 111 | + await using var tx = await _db.CreateTransactionAsync(); |
| 112 | + var linkIdResult = (int?)await tx.ExecuteSqlScalarAsync(""" |
| 113 | + INSERT INTO app.links (reddit_post_id, link_url, link_type, created_at, is_deleted, owner) |
| 114 | + VALUES (@redditPostId, @linkUrl, @linkType, NOW(), false, @userId) |
| 115 | + RETURNING link_id |
| 116 | + """, |
| 117 | + new NpgsqlParameter[] |
| 118 | + { |
| 119 | + new("@redditPostId", link.RedditPostId), |
| 120 | + new("@linkUrl", link.LinkUrl), |
| 121 | + new("@linkType", link.LinkType.RawValue), |
| 122 | + new("@userId", link.OwnerUserId) |
| 123 | + }); |
| 124 | + |
| 125 | + if (!linkIdResult.ToMaybe().Try(out var linkId)) |
| 126 | + throw new ApplicationException("Failed to create link"); |
| 127 | + |
| 128 | + await QueueRedditPostIdForUpdate(tx, link.RedditPostId); |
| 129 | + |
| 130 | + await tx.CommitAsync(); |
| 131 | + |
| 132 | + return Result.Ok<int, CreateLinkError>(linkId); |
| 133 | + } |
| 134 | + catch (PostgresException ex) when (ex is { SqlState: PostgresErrorCodes.UniqueViolation, ConstraintName: "UQ_reddit_post_id_link_url_link_type_is_deleted_owner" }) |
| 135 | + { |
| 136 | + return Result.Fail<int, CreateLinkError>(new LinkAlreadyExists()); |
| 137 | + } |
118 | 138 | } |
119 | 139 |
|
120 | 140 | public async Task<IReadOnlyCollection<Link>> GetAllLinksByUserId(int userId) |
|
0 commit comments