@gonextgames/utils 0.0.15
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/dist/client/analytics/index.js +16 -0
- package/dist/client/analytics/trackEvent.js +21 -0
- package/dist/client/auth/authStore.js +34 -0
- package/dist/client/auth/index.js +157 -0
- package/dist/client/components/AdBar.js +64 -0
- package/dist/client/components/index.js +13 -0
- package/dist/client/index.js +49 -0
- package/dist/client/useStripe.js +34 -0
- package/dist/server/auth/index.js +103 -0
- package/dist/server/auth/passwordHashing.js +29 -0
- package/dist/server/auth/tokens.js +46 -0
- package/dist/server/dynamoDb.js +20 -0
- package/dist/server/email.js +48 -0
- package/dist/server/index.js +71 -0
- package/dist/server/openAI.js +32 -0
- package/dist/server/s3.js +93 -0
- package/dist/server/users.js +151 -0
- package/package.json +54 -0
- package/readme.md +129 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
4
|
+
value: true
|
5
|
+
});
|
6
|
+
var _trackEvent = require("./trackEvent");
|
7
|
+
Object.keys(_trackEvent).forEach(function (key) {
|
8
|
+
if (key === "default" || key === "__esModule") return;
|
9
|
+
if (key in exports && exports[key] === _trackEvent[key]) return;
|
10
|
+
Object.defineProperty(exports, key, {
|
11
|
+
enumerable: true,
|
12
|
+
get: function () {
|
13
|
+
return _trackEvent[key];
|
14
|
+
}
|
15
|
+
});
|
16
|
+
});
|
@@ -0,0 +1,21 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
4
|
+
value: true
|
5
|
+
});
|
6
|
+
exports.trackEvent = void 0;
|
7
|
+
const trackEvent = function (eventName, eventParams) {
|
8
|
+
if (process.env.ENVIRONMENT === 'development') {
|
9
|
+
const eventKey = `${eventName}-${JSON.stringify(eventParams)}`;
|
10
|
+
if (window._lastTrackedEvent === eventKey) {
|
11
|
+
return;
|
12
|
+
}
|
13
|
+
window._lastTrackedEvent = eventKey;
|
14
|
+
}
|
15
|
+
|
16
|
+
// console.log('trackEvent', eventName, eventParams);
|
17
|
+
if (typeof window !== 'undefined' && window.gtag) {
|
18
|
+
window.gtag('event', eventName, eventParams);
|
19
|
+
}
|
20
|
+
};
|
21
|
+
exports.trackEvent = trackEvent;
|
@@ -0,0 +1,34 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
4
|
+
value: true
|
5
|
+
});
|
6
|
+
exports.useAuthStore = void 0;
|
7
|
+
var _zustand = require("zustand");
|
8
|
+
const useAuthStore = exports.useAuthStore = (0, _zustand.create)(set => ({
|
9
|
+
user: null,
|
10
|
+
isAuthenticated: false,
|
11
|
+
isLoading: false,
|
12
|
+
refreshing: false,
|
13
|
+
lastVerified: null,
|
14
|
+
error: null,
|
15
|
+
setAuth: user => {
|
16
|
+
set({
|
17
|
+
user,
|
18
|
+
isAuthenticated: true,
|
19
|
+
isLoading: false
|
20
|
+
});
|
21
|
+
},
|
22
|
+
clearAuth: () => {
|
23
|
+
set({
|
24
|
+
user: null,
|
25
|
+
isAuthenticated: false,
|
26
|
+
isLoading: false
|
27
|
+
});
|
28
|
+
},
|
29
|
+
setLoading: loading => {
|
30
|
+
set({
|
31
|
+
isLoading: loading
|
32
|
+
});
|
33
|
+
}
|
34
|
+
}));
|
@@ -0,0 +1,157 @@
|
|
1
|
+
"use strict";
|
2
|
+
"use client";
|
3
|
+
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
5
|
+
value: true
|
6
|
+
});
|
7
|
+
exports.AuthProvider = AuthProvider;
|
8
|
+
exports.useAuth = useAuth;
|
9
|
+
var _react = require("react");
|
10
|
+
var _navigation = require("next/navigation");
|
11
|
+
var _authStore = require("./authStore");
|
12
|
+
const AuthContext = /*#__PURE__*/(0, _react.createContext)({});
|
13
|
+
function AuthProvider({
|
14
|
+
children
|
15
|
+
}) {
|
16
|
+
const {
|
17
|
+
setAuth,
|
18
|
+
clearAuth,
|
19
|
+
setLoading,
|
20
|
+
setError
|
21
|
+
} = (0, _authStore.useAuthStore)();
|
22
|
+
const router = (0, _navigation.useRouter)();
|
23
|
+
(0, _react.useEffect)(() => {
|
24
|
+
// Only check auth on initial mount
|
25
|
+
const initialCheck = async () => {
|
26
|
+
// Skip auth check on login/register pages
|
27
|
+
if (window.location.pathname === '/login' || window.location.pathname === '/register') {
|
28
|
+
setLoading(false);
|
29
|
+
return;
|
30
|
+
}
|
31
|
+
await checkAuth();
|
32
|
+
};
|
33
|
+
initialCheck();
|
34
|
+
}, []);
|
35
|
+
const checkAuth = async () => {
|
36
|
+
const randomNumber = Math.floor(Math.random() * 100);
|
37
|
+
// if (useAuthStore.getState().isLoading) {
|
38
|
+
// console.warn('[Auth] Already loading', randomNumber)
|
39
|
+
// return // Prevent concurrent checks
|
40
|
+
// }
|
41
|
+
setLoading(true);
|
42
|
+
try {
|
43
|
+
const response = await fetch('/api/auth/verify', {
|
44
|
+
credentials: 'include'
|
45
|
+
});
|
46
|
+
if (!response.ok) {
|
47
|
+
console.warn('[Auth] Verification failed', randomNumber);
|
48
|
+
throw new Error('Verification failed', randomNumber);
|
49
|
+
}
|
50
|
+
const data = await response.json();
|
51
|
+
console.log('[Auth] Verification successful', data);
|
52
|
+
if (!data.authenticated) {
|
53
|
+
console.warn('[Auth] Not authenticated', randomNumber);
|
54
|
+
clearAuth();
|
55
|
+
return;
|
56
|
+
}
|
57
|
+
if (!data.user || !data.user.user_id) {
|
58
|
+
console.warn('[Auth] Invalid user data', randomNumber);
|
59
|
+
throw new Error('Invalid user data', randomNumber);
|
60
|
+
}
|
61
|
+
setAuth(data.user);
|
62
|
+
} catch (error) {
|
63
|
+
console.error('[Auth] Check failed:', error, randomNumber);
|
64
|
+
setError(error);
|
65
|
+
clearAuth();
|
66
|
+
} finally {
|
67
|
+
setLoading(false);
|
68
|
+
}
|
69
|
+
};
|
70
|
+
const login = async (email, password) => {
|
71
|
+
try {
|
72
|
+
const response = await fetch('/api/auth/login', {
|
73
|
+
method: 'POST',
|
74
|
+
headers: {
|
75
|
+
'Content-Type': 'application/json'
|
76
|
+
},
|
77
|
+
body: JSON.stringify({
|
78
|
+
email,
|
79
|
+
password
|
80
|
+
})
|
81
|
+
});
|
82
|
+
if (!response.ok) throw new Error('Login failed');
|
83
|
+
const data = await response.json();
|
84
|
+
await setAuth(data.user);
|
85
|
+
|
86
|
+
// Verify auth state is set
|
87
|
+
const verifyResponse = await fetch('/api/auth/verify');
|
88
|
+
if (!verifyResponse.ok) throw new Error('Auth verification failed');
|
89
|
+
} catch (error) {
|
90
|
+
clearAuth();
|
91
|
+
throw error;
|
92
|
+
}
|
93
|
+
};
|
94
|
+
const logout = async () => {
|
95
|
+
try {
|
96
|
+
await fetch('/api/auth/logout', {
|
97
|
+
method: 'POST'
|
98
|
+
});
|
99
|
+
} catch (error) {
|
100
|
+
// Ignore logout errors
|
101
|
+
} finally {
|
102
|
+
clearAuth();
|
103
|
+
router.push('/');
|
104
|
+
}
|
105
|
+
};
|
106
|
+
const register = async userData => {
|
107
|
+
try {
|
108
|
+
const res = await fetch('/api/auth/register', {
|
109
|
+
method: 'POST',
|
110
|
+
headers: {
|
111
|
+
'Content-Type': 'application/json'
|
112
|
+
},
|
113
|
+
body: JSON.stringify(userData)
|
114
|
+
});
|
115
|
+
const data = await res.json();
|
116
|
+
if (!res.ok) {
|
117
|
+
throw new Error(data.error || 'Registration failed');
|
118
|
+
}
|
119
|
+
setAuth(data.user);
|
120
|
+
} catch (error) {
|
121
|
+
throw error;
|
122
|
+
}
|
123
|
+
};
|
124
|
+
const value = {
|
125
|
+
isAuthenticated: (0, _authStore.useAuthStore)().isAuthenticated,
|
126
|
+
user: (0, _authStore.useAuthStore)().user,
|
127
|
+
isLoading: (0, _authStore.useAuthStore)().isLoading,
|
128
|
+
login,
|
129
|
+
logout,
|
130
|
+
register
|
131
|
+
};
|
132
|
+
return /*#__PURE__*/React.createElement(AuthContext.Provider, {
|
133
|
+
value: value
|
134
|
+
}, children);
|
135
|
+
}
|
136
|
+
|
137
|
+
// Export the hook directly with the function declaration
|
138
|
+
function useAuth() {
|
139
|
+
const {
|
140
|
+
user,
|
141
|
+
isAuthenticated,
|
142
|
+
isLoading
|
143
|
+
} = (0, _authStore.useAuthStore)();
|
144
|
+
const {
|
145
|
+
login,
|
146
|
+
logout,
|
147
|
+
register
|
148
|
+
} = (0, _react.useContext)(AuthContext);
|
149
|
+
return {
|
150
|
+
user,
|
151
|
+
isAuthenticated,
|
152
|
+
isLoading,
|
153
|
+
login,
|
154
|
+
logout,
|
155
|
+
register
|
156
|
+
};
|
157
|
+
}
|
@@ -0,0 +1,64 @@
|
|
1
|
+
"use strict";
|
2
|
+
'use client';
|
3
|
+
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
5
|
+
value: true
|
6
|
+
});
|
7
|
+
exports.default = AdBar;
|
8
|
+
var _react = require("react");
|
9
|
+
var _trackEvent = require("../analytics/trackEvent");
|
10
|
+
const defaultLinks = [{
|
11
|
+
url: 'https://randomgameidea.com',
|
12
|
+
title: 'Random Game Idea',
|
13
|
+
description: 'Generate unique board game ideas instantly by combining mechanics, themes, and turn orders.'
|
14
|
+
}, {
|
15
|
+
url: 'https://templative.net',
|
16
|
+
title: 'Templative',
|
17
|
+
description: 'Batch board game art updates, export to Tabletop Simulator, pdf, and the GameCrafter.'
|
18
|
+
}, {
|
19
|
+
url: 'https://playtestfeedback.com',
|
20
|
+
title: 'Playtest Feedback',
|
21
|
+
description: 'Organize playtests, collect feedback, take photos, build your mailing list, and improve your games.'
|
22
|
+
}, {
|
23
|
+
url: 'https://boardgameprototypes.com',
|
24
|
+
title: 'Board Game Prototypes',
|
25
|
+
description: 'Share your prototypes and sellsheets with publishers.'
|
26
|
+
}];
|
27
|
+
function AdBar({
|
28
|
+
via = "",
|
29
|
+
links = defaultLinks,
|
30
|
+
defaultIcon = '/default-favicon.png'
|
31
|
+
}) {
|
32
|
+
const [activePopover, setActivePopover] = (0, _react.useState)(null);
|
33
|
+
return /*#__PURE__*/React.createElement("div", {
|
34
|
+
className: "adbar"
|
35
|
+
}, links.map((link, index) => /*#__PURE__*/React.createElement("div", {
|
36
|
+
key: index,
|
37
|
+
className: "adbar-item-wrapper",
|
38
|
+
onMouseEnter: () => setActivePopover(index),
|
39
|
+
onMouseLeave: () => setActivePopover(null)
|
40
|
+
}, /*#__PURE__*/React.createElement("a", {
|
41
|
+
href: link.url + (via ? `?via=${via}` : '')
|
42
|
+
// target="_blank"
|
43
|
+
// rel="noopener noreferrer"
|
44
|
+
,
|
45
|
+
onClick: e => {
|
46
|
+
e.preventDefault();
|
47
|
+
(0, _trackEvent.trackEvent)(`adbar_${link.title}_click`);
|
48
|
+
window.location.href = link.url + (via ? `?via=${via}` : '');
|
49
|
+
},
|
50
|
+
className: "adbar-icon"
|
51
|
+
}, /*#__PURE__*/React.createElement("img", {
|
52
|
+
src: `${link.url}/icon.ico`,
|
53
|
+
alt: link.title || `Visit ${link.url}`,
|
54
|
+
onError: e => {
|
55
|
+
e.target.src = defaultIcon;
|
56
|
+
}
|
57
|
+
})), activePopover === index && /*#__PURE__*/React.createElement("div", {
|
58
|
+
className: "custom-popover"
|
59
|
+
}, link.title && /*#__PURE__*/React.createElement("div", {
|
60
|
+
className: "popover-title"
|
61
|
+
}, link.title || new URL(link.url || link).hostname), link.description && /*#__PURE__*/React.createElement("div", {
|
62
|
+
className: "popover-content"
|
63
|
+
}, link.description)))));
|
64
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
4
|
+
value: true
|
5
|
+
});
|
6
|
+
Object.defineProperty(exports, "AdBar", {
|
7
|
+
enumerable: true,
|
8
|
+
get: function () {
|
9
|
+
return _AdBar.default;
|
10
|
+
}
|
11
|
+
});
|
12
|
+
var _AdBar = _interopRequireDefault(require("./AdBar"));
|
13
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
@@ -0,0 +1,49 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
4
|
+
value: true
|
5
|
+
});
|
6
|
+
var _auth = require("./auth");
|
7
|
+
Object.keys(_auth).forEach(function (key) {
|
8
|
+
if (key === "default" || key === "__esModule") return;
|
9
|
+
if (key in exports && exports[key] === _auth[key]) return;
|
10
|
+
Object.defineProperty(exports, key, {
|
11
|
+
enumerable: true,
|
12
|
+
get: function () {
|
13
|
+
return _auth[key];
|
14
|
+
}
|
15
|
+
});
|
16
|
+
});
|
17
|
+
var _analytics = require("./analytics");
|
18
|
+
Object.keys(_analytics).forEach(function (key) {
|
19
|
+
if (key === "default" || key === "__esModule") return;
|
20
|
+
if (key in exports && exports[key] === _analytics[key]) return;
|
21
|
+
Object.defineProperty(exports, key, {
|
22
|
+
enumerable: true,
|
23
|
+
get: function () {
|
24
|
+
return _analytics[key];
|
25
|
+
}
|
26
|
+
});
|
27
|
+
});
|
28
|
+
var _components = require("./components");
|
29
|
+
Object.keys(_components).forEach(function (key) {
|
30
|
+
if (key === "default" || key === "__esModule") return;
|
31
|
+
if (key in exports && exports[key] === _components[key]) return;
|
32
|
+
Object.defineProperty(exports, key, {
|
33
|
+
enumerable: true,
|
34
|
+
get: function () {
|
35
|
+
return _components[key];
|
36
|
+
}
|
37
|
+
});
|
38
|
+
});
|
39
|
+
var _useStripe = require("./useStripe");
|
40
|
+
Object.keys(_useStripe).forEach(function (key) {
|
41
|
+
if (key === "default" || key === "__esModule") return;
|
42
|
+
if (key in exports && exports[key] === _useStripe[key]) return;
|
43
|
+
Object.defineProperty(exports, key, {
|
44
|
+
enumerable: true,
|
45
|
+
get: function () {
|
46
|
+
return _useStripe[key];
|
47
|
+
}
|
48
|
+
});
|
49
|
+
});
|
@@ -0,0 +1,34 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
4
|
+
value: true
|
5
|
+
});
|
6
|
+
exports.useStripe = useStripe;
|
7
|
+
function useStripe() {
|
8
|
+
const initiateDonation = async (amount = 500) => {
|
9
|
+
try {
|
10
|
+
const response = await fetch('https://api.templative.net/stripe/create-donation-session', {
|
11
|
+
method: 'POST',
|
12
|
+
headers: {
|
13
|
+
'Content-Type': 'application/json'
|
14
|
+
},
|
15
|
+
body: JSON.stringify({
|
16
|
+
amount
|
17
|
+
})
|
18
|
+
});
|
19
|
+
const session = await response.json();
|
20
|
+
if (session.error) {
|
21
|
+
throw new Error('Error creating donation session');
|
22
|
+
}
|
23
|
+
|
24
|
+
// Instead of using Stripe.js, directly redirect to the checkout URL
|
25
|
+
window.location.href = session.url;
|
26
|
+
} catch (error) {
|
27
|
+
console.error('Donation process error:', error);
|
28
|
+
alert('Error processing donation');
|
29
|
+
}
|
30
|
+
};
|
31
|
+
return {
|
32
|
+
initiateDonation
|
33
|
+
};
|
34
|
+
}
|
@@ -0,0 +1,103 @@
|
|
1
|
+
"use strict";
|
2
|
+
'use server';
|
3
|
+
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
5
|
+
value: true
|
6
|
+
});
|
7
|
+
exports.getUserFromToken = getUserFromToken;
|
8
|
+
exports.loginWithEmailAndPasswordAndSetToken = loginWithEmailAndPasswordAndSetToken;
|
9
|
+
exports.registerUserAndSetToken = registerUserAndSetToken;
|
10
|
+
var _users = require("../users");
|
11
|
+
var _passwordHashing = require("./passwordHashing.js");
|
12
|
+
var _uuid = require("uuid");
|
13
|
+
async function getUserFromToken(tableName, tokenName) {
|
14
|
+
try {
|
15
|
+
if (!tableName) throw new Error('Table name is required to getUserFromToken.');
|
16
|
+
const verifiedUser = await getUserIdAndEmailFromToken(tokenName);
|
17
|
+
if (!verifiedUser || !verifiedUser.user_id || !verifiedUser.email) {
|
18
|
+
console.warn('No user ID or email found in token', verifiedUser);
|
19
|
+
return null;
|
20
|
+
}
|
21
|
+
const user = await (0, _users.getUser)(verifiedUser.user_id, tableName);
|
22
|
+
if (!user) {
|
23
|
+
console.warn('User not found in database');
|
24
|
+
cookieStore.delete(tokenName);
|
25
|
+
return null;
|
26
|
+
}
|
27
|
+
if (user.email !== verifiedUser.email) {
|
28
|
+
console.warn('User email mismatch');
|
29
|
+
cookieStore.delete(tokenName);
|
30
|
+
return null;
|
31
|
+
}
|
32
|
+
const {
|
33
|
+
password,
|
34
|
+
...safeUser
|
35
|
+
} = user;
|
36
|
+
return safeUser;
|
37
|
+
} catch (error) {
|
38
|
+
console.error('Auth check failed:', error.message);
|
39
|
+
return null;
|
40
|
+
}
|
41
|
+
}
|
42
|
+
async function loginWithEmailAndPasswordAndSetToken(email, password, tableName, tokenName) {
|
43
|
+
try {
|
44
|
+
if (!tableName) throw new Error('Table name is required to loginWithEmailAndPasswordAndSetToken.');
|
45
|
+
if (!tokenName) throw new Error('Token name is required to loginWithEmailAndPasswordAndSetToken.');
|
46
|
+
const user = await (0, _users.getUserByEmail)(email, tableName);
|
47
|
+
if (!user) {
|
48
|
+
console.warn('User not found for email:', email);
|
49
|
+
throw new Error('User not found');
|
50
|
+
}
|
51
|
+
const isValidPassword = await (0, _passwordHashing.comparePasswords)(password, user.password);
|
52
|
+
if (!isValidPassword) {
|
53
|
+
console.warn('Invalid password for user:', email);
|
54
|
+
throw new Error('Invalid password');
|
55
|
+
}
|
56
|
+
await setToken(tokenName, user.user_id, user.email);
|
57
|
+
const {
|
58
|
+
password: _,
|
59
|
+
...userWithoutPassword
|
60
|
+
} = user;
|
61
|
+
return userWithoutPassword;
|
62
|
+
} catch (error) {
|
63
|
+
console.error('Error in loginWithEmailAndPasswordAndSetToken:', error.message);
|
64
|
+
throw error;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
async function registerUserAndSetToken(name, email, password, otherData, tableName) {
|
68
|
+
try {
|
69
|
+
if (!tableName) throw new Error('Table name is required');
|
70
|
+
|
71
|
+
// Check if user exists
|
72
|
+
const existingUser = await (0, _users.getUserByEmail)(email, tableName);
|
73
|
+
if (existingUser) {
|
74
|
+
console.warn('Email already registered:', email);
|
75
|
+
throw new Error('Email already registered');
|
76
|
+
}
|
77
|
+
|
78
|
+
// Hash password
|
79
|
+
const hashedPassword = await hashPassword(password);
|
80
|
+
|
81
|
+
// Create user object with user_id
|
82
|
+
const user = {
|
83
|
+
user_id: (0, _uuid.v4)(),
|
84
|
+
name,
|
85
|
+
email: email,
|
86
|
+
contact_email: email,
|
87
|
+
password: hashedPassword
|
88
|
+
};
|
89
|
+
await (0, _users.createUser)(user.user_id, user.name, user.email, user.password, otherData, tableName);
|
90
|
+
await setToken('token', user.user_id, user.email);
|
91
|
+
const {
|
92
|
+
password: _,
|
93
|
+
...userWithoutPassword
|
94
|
+
} = user;
|
95
|
+
return userWithoutPassword;
|
96
|
+
} catch (error) {
|
97
|
+
console.error('Error in registerUserAndSetToken:', {
|
98
|
+
message: error.message,
|
99
|
+
stack: error.stack
|
100
|
+
});
|
101
|
+
throw new Error('An error occurred while registering the user');
|
102
|
+
}
|
103
|
+
}
|
@@ -0,0 +1,29 @@
|
|
1
|
+
"use strict";
|
2
|
+
'use server';
|
3
|
+
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
5
|
+
value: true
|
6
|
+
});
|
7
|
+
exports.comparePasswords = comparePasswords;
|
8
|
+
exports.hashPassword = hashPassword;
|
9
|
+
var _bcryptjs = _interopRequireDefault(require("bcryptjs"));
|
10
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
11
|
+
async function hashPassword(password) {
|
12
|
+
try {
|
13
|
+
return await _bcryptjs.default.hash(password, 10);
|
14
|
+
} catch (error) {
|
15
|
+
console.error('Error in hashPassword:', error);
|
16
|
+
throw error;
|
17
|
+
}
|
18
|
+
}
|
19
|
+
async function comparePasswords(password, hashedPassword) {
|
20
|
+
try {
|
21
|
+
return await _bcryptjs.default.compare(password, hashedPassword);
|
22
|
+
} catch (error) {
|
23
|
+
console.error('Error in comparePasswords:', {
|
24
|
+
message: error.message,
|
25
|
+
stack: error.stack
|
26
|
+
});
|
27
|
+
throw error;
|
28
|
+
}
|
29
|
+
}
|
@@ -0,0 +1,46 @@
|
|
1
|
+
"use strict";
|
2
|
+
'use server';
|
3
|
+
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
5
|
+
value: true
|
6
|
+
});
|
7
|
+
exports.getUserIdAndEmailFromToken = getUserIdAndEmailFromToken;
|
8
|
+
exports.setToken = setToken;
|
9
|
+
var _headers = require("next/headers");
|
10
|
+
var _jsonwebtoken = _interopRequireDefault(require("jsonwebtoken"));
|
11
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
12
|
+
const JWT_SECRET = process.env.JWT_SECRET_KEY;
|
13
|
+
async function getUserIdAndEmailFromToken(tokenName) {
|
14
|
+
const cookieStore = await (0, _headers.cookies)();
|
15
|
+
const token = cookieStore.get(tokenName)?.value;
|
16
|
+
if (!token) return null;
|
17
|
+
const decodedToken = decodeURIComponent(token);
|
18
|
+
const verifiedUser = _jsonwebtoken.default.verify(decodedToken, JWT_SECRET);
|
19
|
+
if (!verifiedUser || !verifiedUser.user_id || !verifiedUser.exp || !verifiedUser.email) {
|
20
|
+
console.warn('Invalid or expired token', verifiedUser);
|
21
|
+
return null;
|
22
|
+
}
|
23
|
+
const fiveMinutes = 5 * 60 * 1000;
|
24
|
+
if (verifiedUser.exp * 1000 - Date.now() < fiveMinutes) {
|
25
|
+
cookieStore.delete(tokenName);
|
26
|
+
return null;
|
27
|
+
}
|
28
|
+
return verifiedUser;
|
29
|
+
}
|
30
|
+
async function setToken(tokenName, userId, email) {
|
31
|
+
const payload = {
|
32
|
+
user_id: userId,
|
33
|
+
email: email
|
34
|
+
};
|
35
|
+
const token = _jsonwebtoken.default.sign(payload, JWT_SECRET, {
|
36
|
+
expiresIn: '7d'
|
37
|
+
});
|
38
|
+
const cookieStore = (0, _headers.cookies)();
|
39
|
+
cookieStore.set(tokenName, token, {
|
40
|
+
httpOnly: true,
|
41
|
+
secure: process.env.ENVIRONMENT === 'production',
|
42
|
+
sameSite: 'lax',
|
43
|
+
path: '/',
|
44
|
+
maxAge: 7 * 24 * 60 * 60 // 7 days
|
45
|
+
});
|
46
|
+
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
"use strict";
|
2
|
+
"use server";
|
3
|
+
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
5
|
+
value: true
|
6
|
+
});
|
7
|
+
exports.dynamoDb = void 0;
|
8
|
+
var _clientDynamodb = require("@aws-sdk/client-dynamodb");
|
9
|
+
var _libDynamodb = require("@aws-sdk/lib-dynamodb");
|
10
|
+
if (!process.env.AWS_ACCESS_KEY || !process.env.AWS_SECRET) {
|
11
|
+
throw new Error('AWS credentials are not properly configured');
|
12
|
+
}
|
13
|
+
const client = new _clientDynamodb.DynamoDBClient({
|
14
|
+
region: "us-west-2",
|
15
|
+
credentials: {
|
16
|
+
accessKeyId: process.env.AWS_ACCESS_KEY,
|
17
|
+
secretAccessKey: process.env.AWS_SECRET
|
18
|
+
}
|
19
|
+
});
|
20
|
+
const dynamoDb = exports.dynamoDb = _libDynamodb.DynamoDBDocumentClient.from(client);
|
@@ -0,0 +1,48 @@
|
|
1
|
+
"use strict";
|
2
|
+
"use server";
|
3
|
+
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
5
|
+
value: true
|
6
|
+
});
|
7
|
+
exports.sendEmail = sendEmail;
|
8
|
+
var _nodemailer = _interopRequireDefault(require("nodemailer"));
|
9
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
10
|
+
const GONEXTGAMES_EMAIL = process.env.GONEXTGAMES_EMAIL;
|
11
|
+
const GONEXTGAMES_EMAIL_PASSWORD = process.env.GONEXTGAMES_EMAIL_PASSWORD;
|
12
|
+
const transporter = _nodemailer.default.createTransport({
|
13
|
+
service: 'gmail',
|
14
|
+
auth: {
|
15
|
+
user: GONEXTGAMES_EMAIL,
|
16
|
+
pass: GONEXTGAMES_EMAIL_PASSWORD
|
17
|
+
}
|
18
|
+
});
|
19
|
+
async function sendEmail({
|
20
|
+
to,
|
21
|
+
subject,
|
22
|
+
html,
|
23
|
+
unsubscribeToken
|
24
|
+
}) {
|
25
|
+
try {
|
26
|
+
var finalHtml = html;
|
27
|
+
if (unsubscribeToken) {
|
28
|
+
const footer = `<p style="font-size: 12px; color: #666;">
|
29
|
+
Our mailing address: 801 W 5th Street Unit 1210, Austin, TX 78703<br>
|
30
|
+
To unsubscribe, <a href="https://templative.net/waitlist/unsubscribe?email=${to}&token=${unsubscribeToken}">click here</a>.</p>`;
|
31
|
+
finalHtml += footer;
|
32
|
+
}
|
33
|
+
const plaintext = finalHtml.replace(/<br\/?>/g, '\n').replace(/<\/p>/g, '\n').replace(/<[^>]*>/g, '').trim();
|
34
|
+
const mailOptions = {
|
35
|
+
from: GONEXTGAMES_EMAIL,
|
36
|
+
to,
|
37
|
+
subject,
|
38
|
+
text: plaintext,
|
39
|
+
html: finalHtml || plaintext
|
40
|
+
};
|
41
|
+
const info = await transporter.sendMail(mailOptions);
|
42
|
+
// console.log('Email sent:', info.messageId);
|
43
|
+
return info;
|
44
|
+
} catch (error) {
|
45
|
+
console.error('Error sending email:', error);
|
46
|
+
throw error;
|
47
|
+
}
|
48
|
+
}
|
@@ -0,0 +1,71 @@
|
|
1
|
+
"use strict";
|
2
|
+
|
3
|
+
Object.defineProperty(exports, "__esModule", {
|
4
|
+
value: true
|
5
|
+
});
|
6
|
+
var _dynamoDb = require("./dynamoDb");
|
7
|
+
Object.keys(_dynamoDb).forEach(function (key) {
|
8
|
+
if (key === "default" || key === "__esModule") return;
|
9
|
+
if (key in exports && exports[key] === _dynamoDb[key]) return;
|
10
|
+
Object.defineProperty(exports, key, {
|
11
|
+
enumerable: true,
|
12
|
+
get: function () {
|
13
|
+
return _dynamoDb[key];
|
14
|
+
}
|
15
|
+
});
|
16
|
+
});
|
17
|
+
var _email = require("./email");
|
18
|
+
Object.keys(_email).forEach(function (key) {
|
19
|
+
if (key === "default" || key === "__esModule") return;
|
20
|
+
if (key in exports && exports[key] === _email[key]) return;
|
21
|
+
Object.defineProperty(exports, key, {
|
22
|
+
enumerable: true,
|
23
|
+
get: function () {
|
24
|
+
return _email[key];
|
25
|
+
}
|
26
|
+
});
|
27
|
+
});
|
28
|
+
var _openAI = require("./openAI");
|
29
|
+
Object.keys(_openAI).forEach(function (key) {
|
30
|
+
if (key === "default" || key === "__esModule") return;
|
31
|
+
if (key in exports && exports[key] === _openAI[key]) return;
|
32
|
+
Object.defineProperty(exports, key, {
|
33
|
+
enumerable: true,
|
34
|
+
get: function () {
|
35
|
+
return _openAI[key];
|
36
|
+
}
|
37
|
+
});
|
38
|
+
});
|
39
|
+
var _s = require("./s3");
|
40
|
+
Object.keys(_s).forEach(function (key) {
|
41
|
+
if (key === "default" || key === "__esModule") return;
|
42
|
+
if (key in exports && exports[key] === _s[key]) return;
|
43
|
+
Object.defineProperty(exports, key, {
|
44
|
+
enumerable: true,
|
45
|
+
get: function () {
|
46
|
+
return _s[key];
|
47
|
+
}
|
48
|
+
});
|
49
|
+
});
|
50
|
+
var _users = require("./users");
|
51
|
+
Object.keys(_users).forEach(function (key) {
|
52
|
+
if (key === "default" || key === "__esModule") return;
|
53
|
+
if (key in exports && exports[key] === _users[key]) return;
|
54
|
+
Object.defineProperty(exports, key, {
|
55
|
+
enumerable: true,
|
56
|
+
get: function () {
|
57
|
+
return _users[key];
|
58
|
+
}
|
59
|
+
});
|
60
|
+
});
|
61
|
+
var _auth = require("./auth");
|
62
|
+
Object.keys(_auth).forEach(function (key) {
|
63
|
+
if (key === "default" || key === "__esModule") return;
|
64
|
+
if (key in exports && exports[key] === _auth[key]) return;
|
65
|
+
Object.defineProperty(exports, key, {
|
66
|
+
enumerable: true,
|
67
|
+
get: function () {
|
68
|
+
return _auth[key];
|
69
|
+
}
|
70
|
+
});
|
71
|
+
});
|
@@ -0,0 +1,32 @@
|
|
1
|
+
"use strict";
|
2
|
+
"use server";
|
3
|
+
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
5
|
+
value: true
|
6
|
+
});
|
7
|
+
exports.getChatCompletion = getChatCompletion;
|
8
|
+
var _openai = _interopRequireDefault(require("openai"));
|
9
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
10
|
+
const openai = new _openai.default({
|
11
|
+
apiKey: process.env.OPENAI_API_KEY
|
12
|
+
});
|
13
|
+
async function getChatCompletion(messages) {
|
14
|
+
try {
|
15
|
+
const completion = await openai.chat.completions.create({
|
16
|
+
model: "gpt-4-turbo-preview",
|
17
|
+
messages: [{
|
18
|
+
role: 'system',
|
19
|
+
content: 'When mentioning interesting or important concepts, wrap them in <span> tags. For example: The <span>environmental conditions</span> affect the <span>ecosystem</span>.'
|
20
|
+
}, ...messages.map(msg => ({
|
21
|
+
role: msg.user_id === 'ai' ? 'assistant' : 'user',
|
22
|
+
content: msg.content
|
23
|
+
}))],
|
24
|
+
temperature: 0.7
|
25
|
+
});
|
26
|
+
const content = completion.choices[0].message.content;
|
27
|
+
return content.split('\n').map(line => line.trim()).filter(line => line !== '');
|
28
|
+
} catch (error) {
|
29
|
+
console.error('Error in getChatCompletion:', error);
|
30
|
+
throw error;
|
31
|
+
}
|
32
|
+
}
|
@@ -0,0 +1,93 @@
|
|
1
|
+
"use strict";
|
2
|
+
"use server";
|
3
|
+
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
5
|
+
value: true
|
6
|
+
});
|
7
|
+
exports.generateMd5HashFromImage = generateMd5HashFromImage;
|
8
|
+
exports.getByGuid = getByGuid;
|
9
|
+
exports.uploadImage = uploadImage;
|
10
|
+
exports.uploadPdf = uploadPdf;
|
11
|
+
var _clientS = require("@aws-sdk/client-s3");
|
12
|
+
var _crypto = _interopRequireDefault(require("crypto"));
|
13
|
+
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
|
14
|
+
if (!process.env.AWS_ACCESS_KEY || !process.env.AWS_SECRET) {
|
15
|
+
throw new Error('AWS credentials are not properly configured');
|
16
|
+
}
|
17
|
+
const s3Client = new _clientS.S3Client({
|
18
|
+
region: "us-west-2",
|
19
|
+
credentials: {
|
20
|
+
accessKeyId: process.env.AWS_ACCESS_KEY,
|
21
|
+
secretAccessKey: process.env.AWS_SECRET
|
22
|
+
}
|
23
|
+
});
|
24
|
+
async function getByGuid(bucket, guid) {
|
25
|
+
try {
|
26
|
+
const command = new _clientS.GetObjectCommand({
|
27
|
+
Bucket: bucket,
|
28
|
+
Key: guid
|
29
|
+
});
|
30
|
+
const response = await s3Client.send(command);
|
31
|
+
const str = await response.Body.transformToString();
|
32
|
+
return str;
|
33
|
+
} catch (error) {
|
34
|
+
console.error('Error getting file from S3:', error);
|
35
|
+
throw error;
|
36
|
+
}
|
37
|
+
}
|
38
|
+
async function generateMd5HashFromImage(buffer) {
|
39
|
+
return _crypto.default.createHash('md5').update(buffer).digest('hex');
|
40
|
+
}
|
41
|
+
async function uploadImage(imageBuffer, bucket) {
|
42
|
+
try {
|
43
|
+
// Generate MD5 hash for the image
|
44
|
+
const md5Hash = await generateMd5HashFromImage(imageBuffer);
|
45
|
+
const key = `${md5Hash}.png`;
|
46
|
+
|
47
|
+
// Check if image already exists
|
48
|
+
try {
|
49
|
+
const checkCommand = new _clientS.GetObjectCommand({
|
50
|
+
Bucket: bucket,
|
51
|
+
Key: key
|
52
|
+
});
|
53
|
+
await s3Client.send(checkCommand);
|
54
|
+
} catch (error) {
|
55
|
+
if (error.name === 'NoSuchKey') {
|
56
|
+
// Upload the image if it doesn't exist
|
57
|
+
const uploadCommand = new _clientS.PutObjectCommand({
|
58
|
+
Bucket: bucket,
|
59
|
+
Key: key,
|
60
|
+
Body: imageBuffer,
|
61
|
+
ContentType: 'image/png'
|
62
|
+
});
|
63
|
+
await s3Client.send(uploadCommand);
|
64
|
+
} else {
|
65
|
+
throw error;
|
66
|
+
}
|
67
|
+
}
|
68
|
+
|
69
|
+
// Return the URL of the image
|
70
|
+
return `https://${bucket}.s3.amazonaws.com/${key}`;
|
71
|
+
} catch (error) {
|
72
|
+
console.error('Error uploading image:', error);
|
73
|
+
return null;
|
74
|
+
}
|
75
|
+
}
|
76
|
+
async function uploadPdf(pdfBuffer, filename, bucket) {
|
77
|
+
try {
|
78
|
+
const fileExtension = '.pdf';
|
79
|
+
const randomId = _crypto.default.randomBytes(8).toString('hex');
|
80
|
+
const key = `${randomId}-${filename}`;
|
81
|
+
const uploadCommand = new _clientS.PutObjectCommand({
|
82
|
+
Bucket: bucket,
|
83
|
+
Key: key,
|
84
|
+
Body: pdfBuffer,
|
85
|
+
ContentType: 'application/pdf'
|
86
|
+
});
|
87
|
+
await s3Client.send(uploadCommand);
|
88
|
+
return `https://${bucket}.s3.amazonaws.com/${key}`;
|
89
|
+
} catch (error) {
|
90
|
+
console.error('Error uploading PDF:', error);
|
91
|
+
throw error;
|
92
|
+
}
|
93
|
+
}
|
@@ -0,0 +1,151 @@
|
|
1
|
+
"use strict";
|
2
|
+
"use server";
|
3
|
+
|
4
|
+
Object.defineProperty(exports, "__esModule", {
|
5
|
+
value: true
|
6
|
+
});
|
7
|
+
exports.createUser = createUser;
|
8
|
+
exports.deleteUser = deleteUser;
|
9
|
+
exports.getUser = getUser;
|
10
|
+
exports.getUserByEmail = getUserByEmail;
|
11
|
+
exports.getUsersByIds = getUsersByIds;
|
12
|
+
exports.updateUser = updateUser;
|
13
|
+
var _libDynamodb = require("@aws-sdk/lib-dynamodb");
|
14
|
+
var _dynamoDb = require("./dynamoDb.js");
|
15
|
+
async function createUser(id, name, email, hashedPassword, otherData, tableName) {
|
16
|
+
if (!tableName) throw new Error('Table name is required');
|
17
|
+
try {
|
18
|
+
const item = {
|
19
|
+
user_id: id,
|
20
|
+
name,
|
21
|
+
email: email,
|
22
|
+
password: hashedPassword,
|
23
|
+
...otherData,
|
24
|
+
createdAt: new Date().toISOString()
|
25
|
+
};
|
26
|
+
await _dynamoDb.dynamoDb.send(new _libDynamodb.PutCommand({
|
27
|
+
TableName: tableName,
|
28
|
+
Item: item
|
29
|
+
}));
|
30
|
+
return item;
|
31
|
+
} catch (error) {
|
32
|
+
console.error('Error in createUser:', error);
|
33
|
+
throw new Error('An error occurred while creating the user');
|
34
|
+
}
|
35
|
+
}
|
36
|
+
async function getUser(userId, tableName) {
|
37
|
+
try {
|
38
|
+
if (!tableName) throw new Error('Table name is required');
|
39
|
+
const result = await _dynamoDb.dynamoDb.send(new _libDynamodb.GetCommand({
|
40
|
+
TableName: tableName,
|
41
|
+
Key: {
|
42
|
+
user_id: userId
|
43
|
+
}
|
44
|
+
}));
|
45
|
+
return result.Item;
|
46
|
+
} catch (error) {
|
47
|
+
console.error('Error in getUser:', error);
|
48
|
+
throw new Error('An error occurred while retrieving the user');
|
49
|
+
}
|
50
|
+
}
|
51
|
+
async function updateUser(id, updates, tableName) {
|
52
|
+
if (!tableName) throw new Error('Table name is required');
|
53
|
+
try {
|
54
|
+
const updateExpressions = [];
|
55
|
+
const expressionAttributeValues = {};
|
56
|
+
const expressionAttributeNames = {};
|
57
|
+
Object.entries(updates).forEach(([key, value]) => {
|
58
|
+
updateExpressions.push(`#${key} = :${key}`);
|
59
|
+
expressionAttributeValues[`:${key}`] = value;
|
60
|
+
expressionAttributeNames[`#${key}`] = key;
|
61
|
+
});
|
62
|
+
const result = await _dynamoDb.dynamoDb.send(new _libDynamodb.UpdateCommand({
|
63
|
+
TableName: tableName,
|
64
|
+
Key: {
|
65
|
+
user_id: id
|
66
|
+
},
|
67
|
+
UpdateExpression: `SET ${updateExpressions.join(', ')}`,
|
68
|
+
ExpressionAttributeValues: expressionAttributeValues,
|
69
|
+
ExpressionAttributeNames: expressionAttributeNames,
|
70
|
+
ReturnValues: 'ALL_NEW'
|
71
|
+
}));
|
72
|
+
return result.Attributes;
|
73
|
+
} catch (error) {
|
74
|
+
console.error('Error in updateUser:', error);
|
75
|
+
throw new Error('An error occurred while updating the user');
|
76
|
+
}
|
77
|
+
}
|
78
|
+
async function deleteUser(id, tableName) {
|
79
|
+
try {
|
80
|
+
if (!tableName) throw new Error('Table name is required');
|
81
|
+
await _dynamoDb.dynamoDb.send(new _libDynamodb.DeleteCommand({
|
82
|
+
TableName: tableName,
|
83
|
+
Key: {
|
84
|
+
user_id: id
|
85
|
+
}
|
86
|
+
}));
|
87
|
+
} catch (error) {
|
88
|
+
console.error('Error in deleteUser:', error);
|
89
|
+
throw new Error('An error occurred while deleting the user');
|
90
|
+
}
|
91
|
+
}
|
92
|
+
async function getUserByEmail(email, tableName) {
|
93
|
+
try {
|
94
|
+
if (!tableName) throw new Error('Table name is required');
|
95
|
+
const result = await _dynamoDb.dynamoDb.send(new _libDynamodb.QueryCommand({
|
96
|
+
TableName: tableName,
|
97
|
+
IndexName: 'email-user_id-index',
|
98
|
+
KeyConditionExpression: 'email = :email',
|
99
|
+
ExpressionAttributeValues: {
|
100
|
+
':email': email
|
101
|
+
},
|
102
|
+
Limit: 1
|
103
|
+
}));
|
104
|
+
return result.Items[0];
|
105
|
+
} catch (error) {
|
106
|
+
console.error('Error in getUserByEmail:', error);
|
107
|
+
throw new Error('An error occurred while retrieving the user');
|
108
|
+
}
|
109
|
+
}
|
110
|
+
async function getUsersByIds(userIds, tableName) {
|
111
|
+
try {
|
112
|
+
if (!tableName) throw new Error('Table name is required');
|
113
|
+
// If no userIds provided, return empty array
|
114
|
+
if (!userIds || userIds.length === 0) {
|
115
|
+
return [];
|
116
|
+
}
|
117
|
+
|
118
|
+
// DynamoDB only allows 25 items per BatchGetItem
|
119
|
+
const batchSize = 25;
|
120
|
+
let allUsers = [];
|
121
|
+
|
122
|
+
// Process users in batches
|
123
|
+
for (let i = 0; i < userIds.length; i += batchSize) {
|
124
|
+
const batch = userIds.slice(i, i + batchSize);
|
125
|
+
const result = await _dynamoDb.dynamoDb.send(new _libDynamodb.BatchGetCommand({
|
126
|
+
RequestItems: {
|
127
|
+
[tableName]: {
|
128
|
+
Keys: batch.map(id => ({
|
129
|
+
user_id: id
|
130
|
+
}))
|
131
|
+
}
|
132
|
+
}
|
133
|
+
}));
|
134
|
+
if (result.Responses && result.Responses[tableName]) {
|
135
|
+
// Remove sensitive data from users
|
136
|
+
const safeUsers = result.Responses[tableName].map(user => {
|
137
|
+
const {
|
138
|
+
password,
|
139
|
+
...safeUser
|
140
|
+
} = user;
|
141
|
+
return safeUser;
|
142
|
+
});
|
143
|
+
allUsers = [...allUsers, ...safeUsers];
|
144
|
+
}
|
145
|
+
}
|
146
|
+
return allUsers;
|
147
|
+
} catch (error) {
|
148
|
+
console.error('Error in getUsersByIds:', error);
|
149
|
+
throw new Error('An error occurred while retrieving the users');
|
150
|
+
}
|
151
|
+
}
|
package/package.json
ADDED
@@ -0,0 +1,54 @@
|
|
1
|
+
{
|
2
|
+
"name": "@gonextgames/utils",
|
3
|
+
"exports": {
|
4
|
+
"./client": "./dist/client/index.js",
|
5
|
+
"./client/*": "./dist/client/*.js",
|
6
|
+
"./server": "./dist/server/index.js",
|
7
|
+
"./server/*": "./dist/server/*.js",
|
8
|
+
"./client/auth": "./dist/client/auth/index.jsx",
|
9
|
+
"./server/auth": "./dist/server/auth/index.js"
|
10
|
+
},
|
11
|
+
"version": "0.0.15",
|
12
|
+
"publishConfig": {
|
13
|
+
"registry": "https://registry.npmjs.org/",
|
14
|
+
"access": "public"
|
15
|
+
},
|
16
|
+
"main": "dist/index.js",
|
17
|
+
"module": "dist/index.js",
|
18
|
+
"files": [
|
19
|
+
"dist"
|
20
|
+
],
|
21
|
+
"scripts": {
|
22
|
+
"build": "babel src -d dist --copy-files",
|
23
|
+
"prepare": "npm run build",
|
24
|
+
"dev": "babel src -d dist --copy-files --watch"
|
25
|
+
},
|
26
|
+
"dependencies": {
|
27
|
+
"@aws-sdk/client-dynamodb": "^3.726.1",
|
28
|
+
"@aws-sdk/lib-dynamodb": "^3.726.1",
|
29
|
+
"@aws-sdk/client-s3": "^3.726.1",
|
30
|
+
"axios": "^1.7.9",
|
31
|
+
"bcryptjs": "^2.4.3",
|
32
|
+
"jsonwebtoken": "^9.0.0",
|
33
|
+
"nodemailer": "^6.9.16",
|
34
|
+
"openai": "^4.0.0",
|
35
|
+
"next": "^15.1.4",
|
36
|
+
"next-sitemap": "^4.2.3",
|
37
|
+
"react": "^19.0.0",
|
38
|
+
"react-dom": "^19.0.0",
|
39
|
+
"zustand": "^5.0.3"
|
40
|
+
},
|
41
|
+
"devDependencies": {
|
42
|
+
"@babel/cli": "^7.23.9",
|
43
|
+
"@babel/core": "^7.23.9",
|
44
|
+
"@babel/preset-env": "^7.23.9",
|
45
|
+
"@babel/preset-react": "^7.23.9",
|
46
|
+
"next": "^15.1.4"
|
47
|
+
},
|
48
|
+
"engines": {
|
49
|
+
"node": ">=20.0.0"
|
50
|
+
},
|
51
|
+
"peerDependencies": {
|
52
|
+
"next": "15.1.4"
|
53
|
+
}
|
54
|
+
}
|
package/readme.md
ADDED
@@ -0,0 +1,129 @@
|
|
1
|
+
# Go Next Games Landing Utils
|
2
|
+
|
3
|
+
This is a collection of utilities for the Go Next Games Landing Page.
|
4
|
+
|
5
|
+
## Development
|
6
|
+
|
7
|
+
In your package.json, add the following:
|
8
|
+
```json
|
9
|
+
"@gonextgames/utils-client": "file:/Users/oliverbarnum/Documents/git/utils/client",
|
10
|
+
"@gonextgames/utils-server": "file:/Users/oliverbarnum/Documents/git/utils/server",
|
11
|
+
```
|
12
|
+
|
13
|
+
Then run `npm install`
|
14
|
+
|
15
|
+
Create an .env file:
|
16
|
+
|
17
|
+
```json
|
18
|
+
AWS_ACCESS_KEY=""
|
19
|
+
AWS_SECRET=""
|
20
|
+
ENVIRONMENT=""
|
21
|
+
JWT_SECRET_KEY=""
|
22
|
+
STRIPE_API=""
|
23
|
+
STRIPE_PUBLIC_KEY=""
|
24
|
+
OPENAI_API_KEY=""
|
25
|
+
```
|
26
|
+
Then
|
27
|
+
```
|
28
|
+
cd ./client
|
29
|
+
npm run dev
|
30
|
+
```
|
31
|
+
New Terminal:
|
32
|
+
```
|
33
|
+
cd ./server
|
34
|
+
npm run dev
|
35
|
+
```
|
36
|
+
|
37
|
+
## Auth
|
38
|
+
|
39
|
+
### Client Side Consumption
|
40
|
+
|
41
|
+
- Wrap the layout in `<AuthProvider></AuthProvider>`
|
42
|
+
- In components that need to know whether you are auth'd:
|
43
|
+
|
44
|
+
```js
|
45
|
+
import { useAuth, trackEvent } from '@gonextgames/utils-client'
|
46
|
+
const { isAuthenticated, logout, user } = useAuth()
|
47
|
+
```
|
48
|
+
- To change auth
|
49
|
+
```js
|
50
|
+
import { useAuth } from '@gonextgames/utils-client'
|
51
|
+
// Call these functions to call the backend
|
52
|
+
const { login/register/logout } = useAuth()
|
53
|
+
```
|
54
|
+
When implementing auth, you can use the `redirect` function with the `from` parameter to redirect after logging in. After registering, you can redirect to the getting started page or whatever is appropriate for your application.
|
55
|
+
|
56
|
+
### Server Side Consumption
|
57
|
+
|
58
|
+
- Implement /api/auth/verify for tokens
|
59
|
+
|
60
|
+
```js
|
61
|
+
import { getUserFromToken } from '@gonextgames/utils-server'
|
62
|
+
|
63
|
+
export async function GET(req) {
|
64
|
+
try {
|
65
|
+
const user = await getUserFromToken("NAME_OF_USERS_TABLE", "TOKEN_NAME")
|
66
|
+
if (user === null) {
|
67
|
+
return Response.json({authenticated: false}, { status: 200 })
|
68
|
+
}
|
69
|
+
return Response.json({authenticated: true, user: user})
|
70
|
+
} catch (error) {
|
71
|
+
console.error('Verify token error:', error)
|
72
|
+
return Response.json({ authenticated: false, error: 'Token verification failed' }, { status: 401 })
|
73
|
+
}
|
74
|
+
}
|
75
|
+
```
|
76
|
+
|
77
|
+
- Consume it with
|
78
|
+
|
79
|
+
```js
|
80
|
+
import { getUserFromToken } from '@gonextgames/utils-server'
|
81
|
+
const user = await getUserFromToken("NAME_OF_USERS_TABLE", "TOKEN_NAME")
|
82
|
+
if (!user) {
|
83
|
+
redirect(`/login?from=${encodeURIComponent('/FROM-ENDPOINT')}`)
|
84
|
+
}
|
85
|
+
```
|
86
|
+
|
87
|
+
- Create /api/auth/login /logout and /register
|
88
|
+
|
89
|
+
```js
|
90
|
+
// Register
|
91
|
+
const user = await registerUser(userData.name, userData.email, userData.password, { link: userData.link }, 'bgp_users')
|
92
|
+
|
93
|
+
// Login
|
94
|
+
const user = await loginWithEmailAndPasswordAndSetToken(email, password, 'bgp_users', "token")
|
95
|
+
|
96
|
+
//Logout
|
97
|
+
const cookieStore = await cookies()
|
98
|
+
|
99
|
+
cookieStore.set('token', '', {
|
100
|
+
httpOnly: true,
|
101
|
+
secure: process.env.ENVIRONMENT === 'production',
|
102
|
+
sameSite: 'lax',
|
103
|
+
path: '/',
|
104
|
+
expires: new Date(0)
|
105
|
+
})
|
106
|
+
```
|
107
|
+
|
108
|
+
### Users Table
|
109
|
+
|
110
|
+
The user table is standardized. Calling CRUD for users is handled by the package, where you pass the `TABLE_NAME` to the function every time you want to call it.
|
111
|
+
|
112
|
+
```js
|
113
|
+
await updateUser(user.user_id, {
|
114
|
+
contact_email: email,
|
115
|
+
}, 'bgp_users')
|
116
|
+
```
|
117
|
+
|
118
|
+
Nothing is stopping you from creating new ways to get the information, such as by the users /link, like in Board Game Prototypes.
|
119
|
+
|
120
|
+
The users table must have:
|
121
|
+
|
122
|
+
- user_id
|
123
|
+
- email
|
124
|
+
- name
|
125
|
+
- password
|
126
|
+
|
127
|
+
And a `email-user_id-index` secondary index.
|
128
|
+
|
129
|
+
## Event Tracking
|