aaex-cli 2.0.2 → 2.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/create-aaex-app.js +7 -3
- package/package.json +3 -1
- package/template/src/api/auth/login.ts +53 -0
- package/template/src/api/auth/register.ts +34 -0
- package/template/src/api/auth/validate.ts +18 -0
- package/template/src/models/User.ts +45 -5
- package/template/src/pages/test/[slug].tsx +0 -7
package/README.md
CHANGED
package/create-aaex-app.js
CHANGED
|
@@ -66,9 +66,11 @@ async function createPackageJson() {
|
|
|
66
66
|
"aaex-file-router": "^2.0.0",
|
|
67
67
|
jsonwebtoken: "^9.0.3",
|
|
68
68
|
mongodb: "^7.0.0",
|
|
69
|
+
pg: "^8.16.3",
|
|
70
|
+
|
|
69
71
|
bcrypt: "^6.0.0",
|
|
70
72
|
dotenv: "^17.2.3",
|
|
71
|
-
|
|
73
|
+
aaexjs: "^2.1.5",
|
|
72
74
|
},
|
|
73
75
|
devDependencies: {
|
|
74
76
|
typescript: "~5.9.2",
|
|
@@ -76,6 +78,8 @@ async function createPackageJson() {
|
|
|
76
78
|
"@types/react-dom": "^19.1.9",
|
|
77
79
|
"@types/express": "^5.0.3",
|
|
78
80
|
"@vitejs/plugin-react": "^5.0.2",
|
|
81
|
+
"@types/pg": "^8.16.0",
|
|
82
|
+
|
|
79
83
|
vite: "^7.1.5",
|
|
80
84
|
"cross-env": "^10.0.0",
|
|
81
85
|
"@types/bcrypt": "^6.0.0",
|
|
@@ -108,8 +112,8 @@ async function main() {
|
|
|
108
112
|
message: "Which database do you want to use?",
|
|
109
113
|
choices: [
|
|
110
114
|
"MongoDB",
|
|
111
|
-
"MySQL
|
|
112
|
-
"PostgreSQL
|
|
115
|
+
"MySQL --not implemented",
|
|
116
|
+
"PostgreSQL --not fully implemented",
|
|
113
117
|
],
|
|
114
118
|
default: 0,
|
|
115
119
|
},
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "aaex-cli",
|
|
3
|
-
"version": "2.0
|
|
3
|
+
"version": "2.1.0",
|
|
4
4
|
"description": "Command line interface for creating aaexjs app",
|
|
5
5
|
"license": "ISC",
|
|
6
6
|
"author": "",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
"@types/express": "^5.0.6",
|
|
17
17
|
"@types/jsonwebtoken": "^9.0.10",
|
|
18
18
|
"@types/node": "^24.10.1",
|
|
19
|
+
"@types/pg": "^8.16.0",
|
|
19
20
|
"@types/react": "^19.2.7",
|
|
20
21
|
"@types/react-dom": "^19.2.3",
|
|
21
22
|
"@vitejs/plugin-react": "^5.1.1",
|
|
@@ -24,6 +25,7 @@
|
|
|
24
25
|
"express": "^5.2.1",
|
|
25
26
|
"jsonwebtoken": "^9.0.3",
|
|
26
27
|
"mongodb": "^7.0.0",
|
|
28
|
+
"pg": "^8.16.3",
|
|
27
29
|
"react": "^19.2.1",
|
|
28
30
|
"react-dom": "^19.2.1",
|
|
29
31
|
"react-router": "^7.10.1",
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { Request, Response } from "express";
|
|
2
|
+
import jwt from "jsonwebtoken";
|
|
3
|
+
import UserModel, { LoginUser, UserShape } from "../../models/User";
|
|
4
|
+
|
|
5
|
+
export const POST = async (req: Request, res: Response) => {
|
|
6
|
+
const { email, password }: LoginUser = req.body;
|
|
7
|
+
|
|
8
|
+
if (!email || !password) {
|
|
9
|
+
return res.status(400).json({ error: "Missing fields!" });
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
if (!process.env.JWT_SECRET) {
|
|
13
|
+
console.error("Missing: JWT_SECRET from environment variables");
|
|
14
|
+
return res
|
|
15
|
+
.status(500)
|
|
16
|
+
.json({ error: "Internal server error! Try again later" });
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
const normalizedEmail = email.trim().toLowerCase();
|
|
20
|
+
const user = await UserModel.findOne<UserShape>({ email: normalizedEmail });
|
|
21
|
+
|
|
22
|
+
if (!user) {
|
|
23
|
+
return res.status(400).json({ error: "Invalid email or password" });
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const compared = await UserModel.comparePassword(password, user.password);
|
|
27
|
+
|
|
28
|
+
if (!compared) {
|
|
29
|
+
return res.status(400).json({ error: "Invalid email or password" });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const expiration = process.env.JWT_EXP ?? "24h";
|
|
33
|
+
|
|
34
|
+
const token = jwt.sign(
|
|
35
|
+
{
|
|
36
|
+
id: user.id,
|
|
37
|
+
email: user.email,
|
|
38
|
+
username: user.username,
|
|
39
|
+
},
|
|
40
|
+
process.env.JWT_SECRET as string,
|
|
41
|
+
{ expiresIn: expiration as any } //fixes stupid thing where it wont accept the sring variable because its not number | ms.stringvlue | undefined
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
return res.status(200).json({
|
|
45
|
+
ok: true,
|
|
46
|
+
user: {
|
|
47
|
+
id: user.id,
|
|
48
|
+
name: user.username,
|
|
49
|
+
email: user.email,
|
|
50
|
+
},
|
|
51
|
+
token,
|
|
52
|
+
});
|
|
53
|
+
};
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { Request, Response } from "express";
|
|
2
|
+
import UserModel, { CreateUser, UserShape } from "../../models/User";
|
|
3
|
+
export const POST = async (req: Request, res: Response) => {
|
|
4
|
+
const { email, username, password, confirmPass }: CreateUser = req.body;
|
|
5
|
+
|
|
6
|
+
if (!username || !email || !password || !confirmPass)
|
|
7
|
+
return res.status(400).json({ error: "Missing fields" });
|
|
8
|
+
|
|
9
|
+
if (password !== confirmPass)
|
|
10
|
+
return res.status(400).json({ error: "Passwords do not match" });
|
|
11
|
+
|
|
12
|
+
const exists = await UserModel.findOne<UserShape>({ email });
|
|
13
|
+
if (exists)
|
|
14
|
+
return res
|
|
15
|
+
.status(409)
|
|
16
|
+
.json({ error: "User with that email already exists" });
|
|
17
|
+
|
|
18
|
+
const salt = 10;
|
|
19
|
+
const hashed = await UserModel.hashPassword(password, salt);
|
|
20
|
+
|
|
21
|
+
const created = await UserModel.create<Omit<CreateUser, "confirmPass">>({
|
|
22
|
+
username,
|
|
23
|
+
email,
|
|
24
|
+
password: hashed,
|
|
25
|
+
createdAt: new Date(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
if (!created) {
|
|
29
|
+
return res.status(500).json({ error: "User creation failed" });
|
|
30
|
+
}
|
|
31
|
+
return res
|
|
32
|
+
.status(201)
|
|
33
|
+
.json({ ok: true, insertedId: (created as UserShape).id });
|
|
34
|
+
};
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import jwt from "jsonwebtoken";
|
|
2
|
+
import type { Request, Response } from "express";
|
|
3
|
+
|
|
4
|
+
export async function POST(req: Request, res: Response) {
|
|
5
|
+
const { token } = req.body;
|
|
6
|
+
|
|
7
|
+
if (!process.env.JWT_SECRET) {
|
|
8
|
+
console.error("Missing: JWT_SECRET from environment");
|
|
9
|
+
return res.status(500).json({error: "Internal server error!"});
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
try {
|
|
13
|
+
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
|
14
|
+
return res.status(200).json({ valid: true, user: decoded });
|
|
15
|
+
} catch (err) {
|
|
16
|
+
return res.status(401).json({ valid: false });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
@@ -1,13 +1,53 @@
|
|
|
1
|
-
export
|
|
2
|
-
id: string;
|
|
1
|
+
export interface UserShape {
|
|
2
|
+
id: string; // normalized for both mongodb and postgres
|
|
3
3
|
email: string;
|
|
4
4
|
username: string;
|
|
5
5
|
password: string;
|
|
6
|
+
createdAt: Date;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
//helper types for api and forms
|
|
10
|
+
export type CreateUser = Omit<UserShape, "id"> & {
|
|
6
11
|
confirmPass: string;
|
|
7
12
|
};
|
|
8
13
|
|
|
9
|
-
export type
|
|
14
|
+
export type LoginUser = Pick<UserShape, "email" | "password">;
|
|
15
|
+
|
|
16
|
+
export type CookieUser = Omit<UserShape, "password" | "confirmPass">;
|
|
17
|
+
|
|
18
|
+
//the model
|
|
19
|
+
import { Model } from "aaexjs/database";
|
|
20
|
+
import bcrypt from "bcrypt";
|
|
21
|
+
|
|
22
|
+
export default class User extends Model<User> {
|
|
23
|
+
static collection = "users"; //defines the collection for the users
|
|
10
24
|
|
|
11
|
-
|
|
25
|
+
// ===================================================
|
|
26
|
+
// ============= Collection specific =================
|
|
27
|
+
// ===================================================
|
|
28
|
+
/**
|
|
29
|
+
* Hash a plain password
|
|
30
|
+
* @param password - The password to hash
|
|
31
|
+
* @param salt - The number of salt rounds default 10
|
|
32
|
+
* @returns Promise resolving to the hashed password
|
|
33
|
+
*/
|
|
34
|
+
static async hashPassword(
|
|
35
|
+
password: string,
|
|
36
|
+
salt: number = 10
|
|
37
|
+
): Promise<string> {
|
|
38
|
+
return bcrypt.hash(password, salt);
|
|
39
|
+
}
|
|
12
40
|
|
|
13
|
-
|
|
41
|
+
/**
|
|
42
|
+
* Compare a plain password with a hash
|
|
43
|
+
* @param password - Plain password
|
|
44
|
+
* @param hash - Hashed password
|
|
45
|
+
* @returns Promise resolving to true if match
|
|
46
|
+
*/
|
|
47
|
+
static async comparePassword(
|
|
48
|
+
password: string,
|
|
49
|
+
hash: string
|
|
50
|
+
): Promise<boolean> {
|
|
51
|
+
return bcrypt.compare(password, hash);
|
|
52
|
+
}
|
|
53
|
+
}
|