Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 14b43cb3c5 | |||
| 8f398c35df |
@@ -66,6 +66,8 @@ steps:
|
|||||||
environment:
|
environment:
|
||||||
API_BASE_URL:
|
API_BASE_URL:
|
||||||
from_secret: API_BASE_URL
|
from_secret: API_BASE_URL
|
||||||
|
AUTH_BASE_URL:
|
||||||
|
from_secret: AUTH_BASE_URL
|
||||||
volumes:
|
volumes:
|
||||||
- name: dockersock
|
- name: dockersock
|
||||||
path: /var/run/docker.sock
|
path: /var/run/docker.sock
|
||||||
@@ -76,6 +78,7 @@ steps:
|
|||||||
- |
|
- |
|
||||||
docker build --network=host \
|
docker build --network=host \
|
||||||
--build-arg VITE_API_BASE_URL="$API_BASE_URL" \
|
--build-arg VITE_API_BASE_URL="$API_BASE_URL" \
|
||||||
|
--build-arg VITE_AUTH_BASE_URL="$AUTH_BASE_URL" \
|
||||||
-t apps/blog:$IMAGE_TAG \
|
-t apps/blog:$IMAGE_TAG \
|
||||||
-t apps/blog:latest \
|
-t apps/blog:latest \
|
||||||
/drone/src
|
/drone/src
|
||||||
|
|||||||
@@ -15,7 +15,8 @@ COPY . .
|
|||||||
|
|
||||||
# Build the app
|
# Build the app
|
||||||
ARG VITE_API_BASE_URL
|
ARG VITE_API_BASE_URL
|
||||||
RUN VITE_API_BASE_URL=$VITE_API_BASE_URL npm run build
|
ARG VITE_AUTH_BASE_URL
|
||||||
|
RUN VITE_API_BASE_URL=$VITE_API_BASE_URL VITE_AUTH_BASE_URL=$VITE_AUTH_BASE_URL npm run build
|
||||||
|
|
||||||
# Stage 2: Static file server (BusyBox)
|
# Stage 2: Static file server (BusyBox)
|
||||||
FROM busybox:latest
|
FROM busybox:latest
|
||||||
|
|||||||
2
package-lock.json
generated
2
package-lock.json
generated
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aetoskia-blog-app",
|
"name": "aetoskia-blog-app",
|
||||||
"version": "0.2.1",
|
"version": "0.3.1",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "aetoskia-blog-app",
|
"name": "aetoskia-blog-app",
|
||||||
"version": "0.2.5",
|
"version": "0.3.1",
|
||||||
"private": true,
|
"private": true,
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "vite",
|
"dev": "vite",
|
||||||
|
|||||||
@@ -133,6 +133,7 @@ export default function Profile({
|
|||||||
label="Username"
|
label="Username"
|
||||||
name="username"
|
name="username"
|
||||||
margin="normal"
|
margin="normal"
|
||||||
|
disabled={true}
|
||||||
value={formData.username}
|
value={formData.username}
|
||||||
onChange={handleChange}
|
onChange={handleChange}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import React, { createContext, useState, useEffect, useContext } from 'react';
|
import React, { createContext, useState, useEffect, useContext } from 'react';
|
||||||
import { api } from '../utils/api';
|
import { api, auth } from '../utils/api';
|
||||||
import { AuthorModel } from '../types/models';
|
import { AuthorModel } from '../types/models';
|
||||||
import { AuthContextModel } from '../types/contexts';
|
import { AuthContextModel } from '../types/contexts';
|
||||||
|
|
||||||
@@ -18,7 +18,14 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const res = await api.post('/auth/register', { username, password });
|
const res = await auth.post('/register', { username, password });
|
||||||
|
|
||||||
|
// auto-login
|
||||||
|
// await login(username, password);
|
||||||
|
|
||||||
|
// now create author
|
||||||
|
await api.post('/authors', { name: null, avatar: null });
|
||||||
|
|
||||||
return res.data;
|
return res.data;
|
||||||
} catch (err: any) {
|
} catch (err: any) {
|
||||||
console.error('Registration failed:', err);
|
console.error('Registration failed:', err);
|
||||||
@@ -34,7 +41,7 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(null);
|
setError(null);
|
||||||
|
|
||||||
const res = await api.post('/auth/login', { username, password });
|
const res = await auth.post('/login', { username, password });
|
||||||
const { access_token, user } = res.data;
|
const { access_token, user } = res.data;
|
||||||
|
|
||||||
if (access_token) {
|
if (access_token) {
|
||||||
@@ -99,9 +106,9 @@ export const AuthProvider: React.FC<{ children: React.ReactNode }> = ({ children
|
|||||||
const fetchCurrentUser = async () => {
|
const fetchCurrentUser = async () => {
|
||||||
if (!token) return;
|
if (!token) return;
|
||||||
try {
|
try {
|
||||||
const me = await api.get<{ _id: string; username: string; email: string }>('/auth/me');
|
const me = await auth.get('/me');
|
||||||
|
|
||||||
const author = await api.get<AuthorModel>(`/authors/${me.data._id}`);
|
const author = await api.get<AuthorModel>(`/authors/me`);
|
||||||
|
|
||||||
const fullUser = { ...me.data, ...author.data };
|
const fullUser = { ...me.data, ...author.data };
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,42 @@
|
|||||||
// src/utils/api.ts
|
// src/utils/api.ts
|
||||||
import axios from 'axios';
|
import axios from 'axios';
|
||||||
|
|
||||||
|
const AUTH_BASE = import.meta.env.VITE_AUTH_BASE_URL;
|
||||||
const API_BASE = import.meta.env.VITE_API_BASE_URL;
|
const API_BASE = import.meta.env.VITE_API_BASE_URL;
|
||||||
|
|
||||||
|
//------------------------------------------------------
|
||||||
|
// COMMON TOKEN ATTACHMENT LOGIC
|
||||||
|
//------------------------------------------------------
|
||||||
|
const attachToken = (config: any) => {
|
||||||
|
const token = localStorage.getItem('token');
|
||||||
|
if (token) {
|
||||||
|
config.headers.Authorization = `Bearer ${token}`;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleAuthError = (error: any) => {
|
||||||
|
if (error.response?.status === 401) {
|
||||||
|
console.warn('Token expired or invalid. Logging out...');
|
||||||
|
localStorage.removeItem('token');
|
||||||
|
// Optional: eventBus, redirect, logout callback
|
||||||
|
}
|
||||||
|
return Promise.reject(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
//------------------------------------------------------
|
||||||
|
// AUTH SERVICE CLIENT
|
||||||
|
//------------------------------------------------------
|
||||||
|
export const auth = axios.create({
|
||||||
|
baseURL: AUTH_BASE,
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
//------------------------------------------------------
|
||||||
|
// BLOG SERVICE CLIENT
|
||||||
|
//------------------------------------------------------
|
||||||
export const api = axios.create({
|
export const api = axios.create({
|
||||||
baseURL: API_BASE,
|
baseURL: API_BASE,
|
||||||
headers: {
|
headers: {
|
||||||
@@ -10,24 +44,10 @@ export const api = axios.create({
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// 🔹 Attach token from localStorage before each request
|
// Attach token + 401 handling
|
||||||
api.interceptors.request.use((config) => {
|
api.interceptors.request.use(attachToken);
|
||||||
const token = localStorage.getItem('token');
|
api.interceptors.response.use((res) => res, handleAuthError);
|
||||||
if (token) {
|
|
||||||
config.headers.Authorization = `Bearer ${token}`;
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
});
|
|
||||||
|
|
||||||
// 🔹 Handle expired or invalid tokens globally
|
// Auth service ALSO needs token for /me, /logout, /introspect
|
||||||
api.interceptors.response.use(
|
auth.interceptors.request.use(attachToken);
|
||||||
(response) => response,
|
auth.interceptors.response.use((res) => res, handleAuthError);
|
||||||
(error) => {
|
|
||||||
if (error.response?.status === 401) {
|
|
||||||
console.warn('Token expired or invalid. Logging out...');
|
|
||||||
localStorage.removeItem('token');
|
|
||||||
// Optionally: trigger a redirect or event
|
|
||||||
}
|
|
||||||
return Promise.reject(error);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|||||||
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_API_BASE_URL: string;
|
readonly VITE_API_BASE_URL: string;
|
||||||
|
readonly VITE_AUTH_BASE_URL: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
|||||||
Reference in New Issue
Block a user