Skip to content

Commit d3d4fa7

Browse files
Feat/add walker from user (#29)
* feat(walkers): add POST endpoint to create new walkers Implement a new POST /walkers endpoint that allows authenticated users to add new walkers to their clan. The endpoint accepts optional fields like owner, type, use, ready status, and description, with sensible defaults. This enables clans to programmatically add walkers to their inventory through the API. * refactor(routes): rename walker addition endpoint for clarity The endpoint summary and operationId were changed from 'addWalker' to 'addWalkerFromUser' to better reflect its specific purpose and distinguish it from other potential walker creation methods. * feat(types): add new walker use types Add SCOUT, RAIDER, SUPPORT, HAULER, CRAFT, and STORAGE to WalkerUse enum to expand walker functionality classification. * Update src/routes/walkers/index.ts Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com> * fix: include error message in 400 response for missing walker name Previously, when the request body lacked a 'name' field, the endpoint would send an empty 400 response. This change adds a descriptive "Bad Request" message to improve API clarity and client-side debugging. * chore: bump version to 3.7.0 and format keywords array Update the package version from 3.6.0 to 3.7.0 in preparation for a new release. Also reformat the keywords array for improved readability. --------- Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
1 parent 2f6670b commit d3d4fa7

4 files changed

Lines changed: 195 additions & 45 deletions

File tree

package.json

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "stiletto-node-api",
3-
"version": "3.6.0",
3+
"version": "3.7.0",
44
"description": "API for [Stiletto Web](https://github.com/dm94/stiletto-web)",
55
"type": "module",
66
"scripts": {
@@ -17,7 +17,11 @@
1717
"engines": {
1818
"node": ">=20.0.0"
1919
},
20-
"keywords": ["nodejs", "fastify", "typescript"],
20+
"keywords": [
21+
"nodejs",
22+
"fastify",
23+
"typescript"
24+
],
2125
"author": "Daniel Martín",
2226
"bugs": {
2327
"url": "https://github.com/dm94/stiletto-node-api/issues"
@@ -50,4 +54,4 @@
5054
"typescript": "^5.4.5",
5155
"vitest": "^2.1.8"
5256
}
53-
}
57+
}

src/routes/walkers/index.ts

Lines changed: 167 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,60 +1,74 @@
1-
import type { FastifyPluginAsync } from 'fastify';
2-
import { Type } from '@sinclair/typebox';
3-
import { type WalkerInfo, WalkerSchema, WalkerType, WalkerUse } from '@customtypes/walkers';
4-
import type { GetWalkersRequest } from '@customtypes/requests/walkers';
5-
import { Error401Default, Error405Default, Error503Default } from '@customtypes/errors';
1+
import type { FastifyPluginAsync } from "fastify";
2+
import { Type } from "@sinclair/typebox";
3+
import {
4+
type WalkerInfo,
5+
WalkerSchema,
6+
WalkerType,
7+
WalkerUse,
8+
} from "@customtypes/walkers";
9+
import type {
10+
AddWalkersRequest,
11+
GetWalkersRequest,
12+
} from "@customtypes/requests/walkers";
13+
import {
14+
Error400Default,
15+
Error401Default,
16+
Error405Default,
17+
Error503Default,
18+
} from "@customtypes/errors";
19+
import { randomUUID } from "node:crypto";
620

721
const routes: FastifyPluginAsync = async (server) => {
822
server.get<GetWalkersRequest, { Reply: WalkerInfo[] }>(
9-
'/',
23+
"/",
1024
{
1125
onRequest: [server.authenticate],
1226
schema: {
13-
description: 'Return walkers from a discord server',
14-
summary: 'getWalkers',
15-
operationId: 'getWalkers',
16-
tags: ['walkers'],
27+
description: "Return walkers from a discord server",
28+
summary: "getWalkers",
29+
operationId: "getWalkers",
30+
tags: ["walkers"],
1731
querystring: {
18-
type: 'object',
32+
type: "object",
1933
required: [],
2034
properties: {
2135
pageSize: {
22-
type: 'integer',
36+
type: "integer",
2337
default: 10,
2438
minimum: 1,
2539
maximum: 100,
2640
},
2741
page: {
28-
type: 'integer',
42+
type: "integer",
2943
default: 1,
3044
minimum: 1,
3145
},
3246
name: {
33-
type: 'string',
47+
type: "string",
3448
},
3549
owner: {
36-
type: 'string',
50+
type: "string",
3751
},
3852
lastuser: {
39-
type: 'string',
53+
type: "string",
4054
},
4155
walkerid: {
42-
type: 'string',
56+
type: "string",
4357
},
4458
ready: {
45-
type: 'boolean',
59+
type: "boolean",
4660
},
4761
use: {
48-
type: 'string',
62+
type: "string",
4963
enum: Object.values(WalkerUse),
5064
},
5165
type: {
52-
type: 'string',
53-
description: 'Walker Type: Dinghy, Falco...',
66+
type: "string",
67+
description: "Walker Type: Dinghy, Falco...",
5468
enum: Object.values(WalkerType),
5569
},
5670
description: {
57-
type: 'string',
71+
type: "string",
5872
},
5973
},
6074
},
@@ -74,27 +88,36 @@ const routes: FastifyPluginAsync = async (server) => {
7488
(request, reply) => {
7589
if (!request?.dbuser) {
7690
reply.code(401);
77-
return new Error('Invalid token JWT');
91+
return new Error("Invalid token JWT");
7892
}
7993
if (!request?.dbuser.clanid) {
8094
reply.code(405);
81-
return new Error('No clan');
95+
return new Error("No clan");
8296
}
8397

8498
let pageSize: number =
85-
request.query?.pageSize && request.query?.pageSize > 0 ? request.query.pageSize : 10;
86-
let page: number = request.query?.page && request.query.page > 0 ? request.query.page : 1;
99+
request.query?.pageSize && request.query?.pageSize > 0
100+
? request.query.pageSize
101+
: 10;
102+
let page: number =
103+
request.query?.page && request.query.page > 0 ? request.query.page : 1;
87104

88-
const name: string | undefined = request.query?.name ? `%${request.query.name}%` : undefined;
89-
const owner: string | undefined = request.query?.owner ? request.query.owner : undefined;
105+
const name: string | undefined = request.query?.name
106+
? `%${request.query.name}%`
107+
: undefined;
108+
const owner: string | undefined = request.query?.owner
109+
? request.query.owner
110+
: undefined;
90111
const lastuser: string | undefined = request.query?.lastuser
91112
? request.query.lastuser
92113
: undefined;
93114
const walkerid: string | undefined = request.query?.walkerid
94115
? request.query.walkerid
95116
: undefined;
96117
const ready: boolean | undefined = request.query?.ready ?? undefined;
97-
const type: WalkerType | undefined = request.query?.type ? request.query.type : undefined;
118+
const type: WalkerType | undefined = request.query?.type
119+
? request.query.type
120+
: undefined;
98121
const use: WalkerUse | undefined = request.query?.use ?? undefined;
99122
const description: string | undefined = request.query?.description
100123
? `%${request.query.description}%`
@@ -111,39 +134,39 @@ const routes: FastifyPluginAsync = async (server) => {
111134
const queryValues: unknown[] = [];
112135

113136
let sql =
114-
'SELECT walkers.walkerID as walkerid, walkers.name, walkers.ownerUser, walkers.lastUser as lastuser, walkers.datelastuse, walkers.type, walkers.walker_use, walkers.isReady, walkers.description from walkers, clans where clans.discordid=walkers.discorid and clans.clanid=?';
137+
"SELECT walkers.walkerID as walkerid, walkers.name, walkers.ownerUser, walkers.lastUser as lastuser, walkers.datelastuse, walkers.type, walkers.walker_use, walkers.isReady, walkers.description from walkers, clans where clans.discordid=walkers.discorid and clans.clanid=?";
115138
queryValues.push(request.dbuser.clanid);
116139

117140
if (name) {
118-
sql += ' and walkers.name like ?';
141+
sql += " and walkers.name like ?";
119142
queryValues.push(name);
120143
}
121144
if (owner) {
122-
sql += ' and walkers.ownerUser like ?';
145+
sql += " and walkers.ownerUser like ?";
123146
queryValues.push(owner);
124147
}
125148
if (lastuser) {
126-
sql += ' and walkers.lastUser like ?';
149+
sql += " and walkers.lastUser like ?";
127150
queryValues.push(lastuser);
128151
}
129152
if (walkerid) {
130-
sql += ' and walkers.walkerID=?';
153+
sql += " and walkers.walkerID=?";
131154
queryValues.push(walkerid);
132155
}
133156
if (ready !== undefined) {
134-
sql += ' and walkers.isReady=?';
157+
sql += " and walkers.isReady=?";
135158
queryValues.push(ready);
136159
}
137160
if (use) {
138-
sql += ' and walkers.walker_use=?';
161+
sql += " and walkers.walker_use=?";
139162
queryValues.push(use);
140163
}
141164
if (description) {
142-
sql += ' and walkers.description like ?';
165+
sql += " and walkers.description like ?";
143166
queryValues.push(description);
144167
}
145168
if (type) {
146-
sql += ' and walkers.type=?';
169+
sql += " and walkers.type=?";
147170
queryValues.push(type);
148171
}
149172

@@ -159,6 +182,112 @@ const routes: FastifyPluginAsync = async (server) => {
159182
});
160183
},
161184
);
185+
server.post<AddWalkersRequest>(
186+
"/",
187+
{
188+
onRequest: [server.authenticate],
189+
schema: {
190+
description: "Add a new walker",
191+
summary: "addWalkerFromUser",
192+
operationId: "addWalkerFromUser",
193+
tags: ["walkers"],
194+
body: {
195+
type: "object",
196+
required: ["name"],
197+
properties: {
198+
name: {
199+
type: "string",
200+
},
201+
owner: {
202+
type: "string",
203+
},
204+
use: {
205+
type: "string",
206+
enum: Object.values(WalkerUse),
207+
},
208+
ready: {
209+
type: "boolean",
210+
},
211+
type: {
212+
type: "string",
213+
enum: Object.values(WalkerType),
214+
},
215+
description: {
216+
type: "string",
217+
},
218+
},
219+
},
220+
security: [
221+
{
222+
token: [],
223+
},
224+
],
225+
response: {
226+
201: Type.Object({
227+
message: Type.String(),
228+
}),
229+
400: Error400Default,
230+
401: Error401Default,
231+
405: Error405Default,
232+
503: Error503Default,
233+
},
234+
},
235+
},
236+
(request, reply) => {
237+
if (!request?.dbuser) {
238+
reply.code(401);
239+
return new Error("Invalid token JWT");
240+
}
241+
if (!request?.dbuser.clanid || !request?.dbuser.serverdiscord) {
242+
reply.code(405);
243+
return new Error("No clan");
244+
}
245+
if (!request?.body?.name) {
246+
return reply.code(400).send("Bad Request");
247+
}
248+
249+
const walkerid: string = randomUUID();
250+
const discordid: string = request.dbuser.serverdiscord;
251+
const name: string = request.body.name;
252+
const lastuser: string = request.dbuser.nickname ?? "";
253+
const owner: string =
254+
request.body.owner ??
255+
request.dbuser.nickname ??
256+
request.dbuser.discordid;
257+
const date = new Date().toISOString().split("T")[0];
258+
const walkerUse: WalkerUse = request.body.use ?? WalkerUse.PERSONAL;
259+
const ready: boolean = request.body.ready ?? false;
260+
const walkerType: WalkerType = request.body.type ?? WalkerType.DINGHY;
261+
const description: string = request.body.description ?? "";
262+
263+
server.mysql.query(
264+
"insert into walkers(walkerID,discorid,name,ownerUser,lastUser,datelastuse, walker_use, type, isReady, description) values(?,?,?,?,?,?,?,?,?,?)",
265+
[
266+
walkerid,
267+
discordid,
268+
name,
269+
owner,
270+
lastuser,
271+
date,
272+
walkerUse,
273+
walkerType,
274+
ready,
275+
description,
276+
],
277+
(insertErr, insertResult) => {
278+
if (insertErr) {
279+
return reply.code(503).send();
280+
}
281+
282+
if (insertResult) {
283+
return reply.code(201).send({
284+
message: "Walker created",
285+
});
286+
}
287+
},
288+
);
289+
},
290+
);
162291
};
163292

164293
export default routes;

src/types/requests/walkers.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,17 @@ export interface GetWalkersRequest extends RequestGenericInterface {
1616
};
1717
}
1818

19+
export interface AddWalkersRequest extends RequestGenericInterface {
20+
Body: {
21+
name: string;
22+
owner?: string;
23+
use?: WalkerUse;
24+
ready?: boolean;
25+
type?: WalkerType;
26+
description?: string;
27+
};
28+
}
29+
1930
export interface EditWalkersRequest extends RequestGenericInterface {
2031
Params: {
2132
walkerid: string;

src/types/walkers.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
import { type Static, Type } from '@sinclair/typebox';
22

33
export enum WalkerUse {
4-
PVP = 'pvp',
5-
FARMING = 'farming',
6-
PERSONAL = 'personal',
7-
RAM = 'ram',
4+
PVP = "pvp",
5+
FARMING = "farming",
6+
PERSONAL = "personal",
7+
RAM = "ram",
8+
SCOUT = "scout",
9+
RAIDER = "raider",
10+
SUPPORT = "support",
11+
HAULER = "hauler",
12+
CRAFT = "craft",
13+
STORAGE = "storage",
814
}
915

1016
export enum WalkerType {

0 commit comments

Comments
 (0)