Repository navigation
Expand file tree
/
Copy pathbotNames.ts
More file actions
141 lines (121 loc) · 3.5 KB
/
Copy pathbotNames.ts
File metadata and controls
141 lines (121 loc) · 3.5 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
import { DeleteCommand, GetCommand, PutCommand } from '@aws-sdk/lib-dynamodb';
import { ddbDocClient } from './ddb.js';
const BOTNAME_PK = 'BOTNAME';
const MAX_BOT_DISPLAY_NAME_LENGTH = 64;
export class BotNameTakenError extends Error {
constructor(message = 'That bot name is already in use') {
super(message);
this.name = 'BotNameTakenError';
}
}
export class BotNameValidationError extends Error {
constructor(message: string) {
super(message);
this.name = 'BotNameValidationError';
}
}
export function normalizeBotDisplayName(name: string): string {
return name.trim().toLowerCase();
}
export function validateBotDisplayName(name: string): string {
const displayName = name.trim();
if (displayName.length === 0) {
throw new BotNameValidationError('A name is required for the bot');
}
if (displayName.length > MAX_BOT_DISPLAY_NAME_LENGTH) {
throw new BotNameValidationError(`Bot name must be at most ${MAX_BOT_DISPLAY_NAME_LENGTH} characters`);
}
return displayName;
}
function getTableName(): string {
const tableName = process.env.ABSTRACT_PLAY_TABLE;
if (!tableName) {
throw new Error('ABSTRACT_PLAY_TABLE environment variable is not set');
}
return tableName;
}
export async function assertBotDisplayNameAvailable(
name: string,
options?: { excludeBotClientId?: string }
): Promise<void> {
const tableName = getTableName();
const normalizedName = normalizeBotDisplayName(name);
const data = await ddbDocClient.send(
new GetCommand({
TableName: tableName,
Key: { pk: BOTNAME_PK, sk: normalizedName },
})
);
if (!data.Item) {
return;
}
const existingClientId = data.Item.clientId as string | undefined;
if (options?.excludeBotClientId && existingClientId === options.excludeBotClientId) {
return;
}
throw new BotNameTakenError();
}
export async function reserveBotDisplayName(
name: string,
clientId: string,
owner: string
): Promise<string> {
const displayName = validateBotDisplayName(name);
const tableName = getTableName();
const normalizedName = normalizeBotDisplayName(displayName);
try {
await ddbDocClient.send(
new PutCommand({
TableName: tableName,
Item: {
pk: BOTNAME_PK,
sk: normalizedName,
clientId,
owner,
displayName,
},
ConditionExpression: 'attribute_not_exists(pk)',
})
);
} catch (error: unknown) {
const err = error as { name?: string };
if (err.name === 'ConditionalCheckFailedException') {
throw new BotNameTakenError();
}
throw error;
}
return displayName;
}
export async function releaseBotDisplayName(name: string): Promise<void> {
const normalizedName = normalizeBotDisplayName(name);
if (!normalizedName) {
return;
}
await ddbDocClient.send(
new DeleteCommand({
TableName: getTableName(),
Key: { pk: BOTNAME_PK, sk: normalizedName },
})
);
}
export async function renameBotDisplayName(
oldName: string,
newName: string,
clientId: string,
owner: string
): Promise<string> {
const displayName = validateBotDisplayName(newName);
const oldNormalized = normalizeBotDisplayName(oldName);
const newNormalized = normalizeBotDisplayName(displayName);
if (oldNormalized === newNormalized) {
return displayName;
}
await reserveBotDisplayName(displayName, clientId, owner);
try {
await releaseBotDisplayName(oldName);
} catch (error) {
await releaseBotDisplayName(displayName);
throw error;
}
return displayName;
}