mirror of
https://github.com/hexolan/panels.git
synced 2026-03-26 20:41:15 +00:00
init frontend
This commit is contained in:
24
services/frontend/src/app/api/auth.ts
Normal file
24
services/frontend/src/app/api/auth.ts
Normal file
@@ -0,0 +1,24 @@
|
||||
import { apiSlice } from '../features/api'
|
||||
import { convertRawAuthData } from '../types/auth'
|
||||
|
||||
import type { AuthData } from '../types/common'
|
||||
import type { LoginRequest, RawAuthResponse } from '../types/auth'
|
||||
|
||||
export const authApiSlice = apiSlice.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
login: builder.mutation<AuthData, LoginRequest>({
|
||||
query: data => ({
|
||||
url: '/v1/auth/login',
|
||||
method: 'POST',
|
||||
body: { ...data }
|
||||
}),
|
||||
transformResponse: (response: RawAuthResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid auth response') }
|
||||
|
||||
return convertRawAuthData(response.data)
|
||||
},
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
export const { useLoginMutation } = authApiSlice
|
||||
68
services/frontend/src/app/api/comments.ts
Normal file
68
services/frontend/src/app/api/comments.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { apiSlice } from '../features/api'
|
||||
import { convertRawComment } from '../types/comments'
|
||||
|
||||
import type { Comment } from '../types/common'
|
||||
import type {
|
||||
RawComment, RawCommentResponse, RawCommentsResponse,
|
||||
GetPostCommentsRequest,
|
||||
UpdatePostCommentRequest,
|
||||
DeletePostCommentRequest,
|
||||
CreatePostCommentRequest
|
||||
} from '../types/comments'
|
||||
|
||||
export const commentsApiSlice = apiSlice.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getPostComments: builder.query<Comment[], GetPostCommentsRequest>({
|
||||
query: data => ({ url: `/v1/posts/${data.postId}/comments` }),
|
||||
transformResponse: (response: RawCommentsResponse) => {
|
||||
if (response.data === undefined) {
|
||||
throw Error('invalid comments response')
|
||||
} else if (!response.data.comments) {
|
||||
return []
|
||||
}
|
||||
|
||||
return response.data.comments.map<Comment>((rawComment: RawComment) => convertRawComment(rawComment))
|
||||
}
|
||||
}),
|
||||
|
||||
updatePostComment: builder.mutation<Comment, UpdatePostCommentRequest>({
|
||||
query: req => ({
|
||||
url: `/v1/posts/${req.postId}/comments/${req.id}`,
|
||||
method: 'PATCH',
|
||||
body: { ...req.data }
|
||||
}),
|
||||
transformResponse: (response: RawCommentResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid comment response') }
|
||||
|
||||
return convertRawComment(response.data)
|
||||
}
|
||||
}),
|
||||
|
||||
deletePostComment: builder.mutation<void, DeletePostCommentRequest>({
|
||||
query: req => ({
|
||||
url: `/v1/posts/${req.postId}/comments/${req.id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}),
|
||||
|
||||
createPostComment: builder.mutation<Comment, CreatePostCommentRequest>({
|
||||
query: req => ({
|
||||
url: `/v1/posts/${req.postId}/comments`,
|
||||
method: 'POST',
|
||||
body: { ...req.data }
|
||||
}),
|
||||
transformResponse: (response: RawCommentResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid comment response') }
|
||||
|
||||
return convertRawComment(response.data)
|
||||
}
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
export const {
|
||||
useGetPostCommentsQuery,
|
||||
useUpdatePostCommentMutation,
|
||||
useDeletePostCommentMutation,
|
||||
useCreatePostCommentMutation
|
||||
} = commentsApiSlice
|
||||
93
services/frontend/src/app/api/panels.ts
Normal file
93
services/frontend/src/app/api/panels.ts
Normal file
@@ -0,0 +1,93 @@
|
||||
import { apiSlice } from '../features/api'
|
||||
import { convertRawPanel } from '../types/panels'
|
||||
|
||||
import type { Panel } from '../types/common'
|
||||
import type {
|
||||
RawPanelResponse,
|
||||
GetPanelByIdRequest, GetPanelByNameRequest,
|
||||
UpdatePanelByIdRequest, UpdatePanelByNameRequest,
|
||||
DeletePanelByIdRequest, DeletePanelByNameRequest,
|
||||
CreatePanelRequest
|
||||
} from '../types/panels'
|
||||
|
||||
export const panelsApiSlice = apiSlice.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getPanelById: builder.query<Panel, GetPanelByIdRequest>({
|
||||
query: req => ({ url: `/v1/panels/id/${req.id}` }),
|
||||
transformResponse: (response: RawPanelResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid panel response') }
|
||||
|
||||
return convertRawPanel(response.data)
|
||||
}
|
||||
}),
|
||||
|
||||
getPanelByName: builder.query<Panel, GetPanelByNameRequest>({
|
||||
query: req => ({ url: `/v1/panels/name/${req.name}` }),
|
||||
transformResponse: (response: RawPanelResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid panel response') }
|
||||
|
||||
return convertRawPanel(response.data)
|
||||
}
|
||||
}),
|
||||
|
||||
updatePanelById: builder.mutation<Panel, UpdatePanelByIdRequest>({
|
||||
query: req => ({
|
||||
url: `/v1/panels/id/${req.id}`,
|
||||
method: 'PATCH',
|
||||
body: { ...req.data }
|
||||
}),
|
||||
transformResponse: (response: RawPanelResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid panel response') }
|
||||
|
||||
return convertRawPanel(response.data)
|
||||
}
|
||||
}),
|
||||
|
||||
updatePanelByName: builder.mutation<Panel, UpdatePanelByNameRequest>({
|
||||
query: req => ({
|
||||
url: `/v1/panels/name/${req.name}`,
|
||||
method: 'PATCH',
|
||||
body: { ...req.data }
|
||||
}),
|
||||
transformResponse: (response: RawPanelResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid panel response') }
|
||||
|
||||
return convertRawPanel(response.data)
|
||||
}
|
||||
}),
|
||||
|
||||
deletePanelById: builder.mutation<void, DeletePanelByIdRequest>({
|
||||
query: req => ({
|
||||
url: `/v1/panels/id/${req.id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}),
|
||||
|
||||
deletePanelByName: builder.mutation<void, DeletePanelByNameRequest>({
|
||||
query: req => ({
|
||||
url: `/v1/panels/id/${req.name}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}),
|
||||
|
||||
createPanel: builder.mutation<Panel, CreatePanelRequest>({
|
||||
query: req => ({
|
||||
url: '/v1/panels',
|
||||
method: 'POST',
|
||||
body: { ...req }
|
||||
}),
|
||||
transformResponse: (response: RawPanelResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid panel response') }
|
||||
|
||||
return convertRawPanel(response.data)
|
||||
}
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
export const {
|
||||
useGetPanelByIdQuery, useGetPanelByNameQuery,
|
||||
useUpdatePanelByIdMutation, useUpdatePanelByNameMutation,
|
||||
useDeletePanelByIdMutation, useDeletePanelByNameMutation,
|
||||
useCreatePanelMutation
|
||||
} = panelsApiSlice
|
||||
107
services/frontend/src/app/api/posts.ts
Normal file
107
services/frontend/src/app/api/posts.ts
Normal file
@@ -0,0 +1,107 @@
|
||||
import { apiSlice } from '../features/api'
|
||||
import { convertRawPost } from '../types/posts'
|
||||
|
||||
import type { Post } from '../types/common'
|
||||
import type {
|
||||
RawPost, RawPostResponse, RawPostsResponse,
|
||||
GetPanelPostRequest, GetPanelPostsRequest,
|
||||
GetUserPostsRequest,
|
||||
UpdatePostRequest,
|
||||
DeletePostRequest,
|
||||
CreatePostRequest
|
||||
} from '../types/posts'
|
||||
|
||||
export const postsApiSlice = apiSlice.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getPanelPost: builder.query<Post, GetPanelPostRequest>({
|
||||
query: req => ({ url: `/v1/panels/id/${req.panelId}/posts/${req.id}` }),
|
||||
transformResponse: (response: RawPostResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid post response') }
|
||||
|
||||
return convertRawPost(response.data)
|
||||
}
|
||||
}),
|
||||
|
||||
getFeedPosts: builder.query<Post[], void>({
|
||||
query: () => '/v1/posts/feed',
|
||||
transformResponse: (response: RawPostsResponse) => {
|
||||
if (response.data === undefined) {
|
||||
throw Error('invalid posts response')
|
||||
} else if (!response.data.posts) {
|
||||
return []
|
||||
}
|
||||
|
||||
return response.data.posts.map<Post>((rawPost: RawPost) => convertRawPost(rawPost))
|
||||
}
|
||||
}),
|
||||
|
||||
getUserPosts: builder.query<Post[], GetUserPostsRequest>({
|
||||
query: req => `/v1/users/id/${req.userId}/posts`,
|
||||
transformResponse: (response: RawPostsResponse) => {
|
||||
if (response.data === undefined) {
|
||||
throw Error('invalid posts response')
|
||||
} else if (!response.data.posts) {
|
||||
return []
|
||||
}
|
||||
|
||||
return response.data.posts.map<Post>((rawPost: RawPost) => convertRawPost(rawPost))
|
||||
}
|
||||
}),
|
||||
|
||||
getPanelPosts: builder.query<Post[], GetPanelPostsRequest>({
|
||||
query: req => `/v1/panels/id/${req.panelId}/posts`,
|
||||
transformResponse: (response: RawPostsResponse) => {
|
||||
if (response.data === undefined) {
|
||||
throw Error('invalid posts response')
|
||||
} else if (!response.data.posts) {
|
||||
return []
|
||||
}
|
||||
|
||||
return response.data.posts.map<Post>((rawPost: RawPost) => convertRawPost(rawPost))
|
||||
}
|
||||
}),
|
||||
|
||||
updatePost: builder.mutation<Post, UpdatePostRequest>({
|
||||
query: req => ({
|
||||
url: `/v1/posts/${req.id}`,
|
||||
method: 'PATCH',
|
||||
body: { ...req.data },
|
||||
}),
|
||||
transformResponse: (response: RawPostResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid post response') }
|
||||
|
||||
return convertRawPost(response.data)
|
||||
}
|
||||
}),
|
||||
|
||||
deletePost: builder.mutation<void, DeletePostRequest>({
|
||||
query: req => ({
|
||||
url: `/v1/posts/${req.id}`,
|
||||
method: 'DELETE',
|
||||
})
|
||||
}),
|
||||
|
||||
createPanelPost: builder.mutation<Post, CreatePostRequest>({
|
||||
query: req => ({
|
||||
url: `/v1/panels/id/${req.panelId}`,
|
||||
method: 'POST',
|
||||
body: { ...req.data },
|
||||
}),
|
||||
transformResponse: (response: RawPostResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid post response') }
|
||||
|
||||
return convertRawPost(response.data)
|
||||
}
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
export const {
|
||||
useGetPanelPostQuery,
|
||||
useGetFeedPostsQuery,
|
||||
useGetUserPostsQuery,
|
||||
useGetPanelPostsQuery,
|
||||
useUpdatePostMutation,
|
||||
useDeletePostMutation,
|
||||
useCreatePanelPostMutation
|
||||
} = postsApiSlice
|
||||
83
services/frontend/src/app/api/users.ts
Normal file
83
services/frontend/src/app/api/users.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { apiSlice } from '../features/api'
|
||||
import { convertRawUser } from '../types/user'
|
||||
import { convertRawAuthData } from '../types/auth'
|
||||
|
||||
import type { User, AuthData } from '../types/common'
|
||||
import type { RawAuthResponse } from '../types/auth'
|
||||
import type {
|
||||
RawUserResponse,
|
||||
GetUserByIdRequest, GetUserByNameRequest,
|
||||
DeleteUserByIdRequest, DeleteUserByNameRequest,
|
||||
RegisterUserRequest
|
||||
} from '../types/user'
|
||||
|
||||
export const usersApiSlice = apiSlice.injectEndpoints({
|
||||
endpoints: (builder) => ({
|
||||
getUserById: builder.query<User, GetUserByIdRequest>({
|
||||
query: req => ({ url: `/v1/users/id/${req.id}` }),
|
||||
transformResponse: (response: RawUserResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid user response') }
|
||||
|
||||
return convertRawUser(response.data)
|
||||
}
|
||||
}),
|
||||
|
||||
getUserByName: builder.query<User, GetUserByNameRequest>({
|
||||
query: req => ({ url: `/v1/users/username/${req.username}` }),
|
||||
transformResponse: (response: RawUserResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid user response') }
|
||||
|
||||
return convertRawUser(response.data)
|
||||
}
|
||||
}),
|
||||
|
||||
getCurrentUser: builder.query<User, void>({
|
||||
query: () => ({ url: '/v1/users/me' }),
|
||||
transformResponse: (response: RawUserResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid user response') }
|
||||
|
||||
return convertRawUser(response.data)
|
||||
}
|
||||
}),
|
||||
|
||||
deleteUserById: builder.mutation<void, DeleteUserByIdRequest>({
|
||||
query: req => ({
|
||||
url: `/v1/users/id/${req.id}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}),
|
||||
|
||||
deleteUserByName: builder.mutation<void, DeleteUserByNameRequest>({
|
||||
query: req => ({
|
||||
url: `/v1/users/username/${req.username}`,
|
||||
method: 'DELETE'
|
||||
})
|
||||
}),
|
||||
|
||||
deleteCurrentUser: builder.mutation<void, void>({
|
||||
query: () => ({
|
||||
url: '/v1/users/me',
|
||||
method: 'DELETE'
|
||||
})
|
||||
}),
|
||||
|
||||
registerUser: builder.mutation<AuthData, RegisterUserRequest>({
|
||||
query: req => ({
|
||||
url: '/v1/users',
|
||||
method: 'POST',
|
||||
body: { ...req }
|
||||
}),
|
||||
transformResponse: (response: RawAuthResponse) => {
|
||||
if (response.data === undefined) { throw Error('invalid registration response') }
|
||||
|
||||
return convertRawAuthData(response.data)
|
||||
}
|
||||
}),
|
||||
})
|
||||
})
|
||||
|
||||
export const {
|
||||
useGetUserByIdQuery, useGetUserByNameQuery, useGetCurrentUserQuery,
|
||||
useDeleteUserByIdMutation, useDeleteUserByNameMutation, useDeleteCurrentUserMutation,
|
||||
useRegisterUserMutation
|
||||
} = usersApiSlice
|
||||
33
services/frontend/src/app/features/api.ts
Normal file
33
services/frontend/src/app/features/api.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import { createApi, fetchBaseQuery } from '@reduxjs/toolkit/query/react';
|
||||
import type { BaseQueryFn } from '@reduxjs/toolkit/query'
|
||||
|
||||
import { setUnauthed } from './auth'
|
||||
import type { RootState } from '../store'
|
||||
|
||||
const baseQuery = fetchBaseQuery({
|
||||
baseUrl: import.meta.env.VITE_API_URL,
|
||||
prepareHeaders: (headers, { getState }) => {
|
||||
const state = getState() as RootState
|
||||
|
||||
const token = state.auth.accessToken
|
||||
if (token) {
|
||||
headers.set('Authorization', `Bearer ${token}`)
|
||||
}
|
||||
|
||||
return headers
|
||||
}
|
||||
})
|
||||
|
||||
const wrappedBaseQuery: BaseQueryFn = async (args, api, extraOptions) => {
|
||||
const result = await baseQuery(args, api, extraOptions)
|
||||
if ((api.getState() as RootState).auth.accessToken && result?.error?.status === 403) {
|
||||
api.dispatch(setUnauthed())
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
export const apiSlice = createApi({
|
||||
baseQuery: wrappedBaseQuery,
|
||||
endpoints: () => ({}),
|
||||
})
|
||||
44
services/frontend/src/app/features/auth.ts
Normal file
44
services/frontend/src/app/features/auth.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import { createSlice } from '@reduxjs/toolkit'
|
||||
|
||||
import { authApiSlice } from '../api/auth'
|
||||
import { usersApiSlice } from '../api/users'
|
||||
import type { User } from '../types/common'
|
||||
|
||||
export interface AuthState {
|
||||
accessToken: string | null;
|
||||
currentUser: User | null;
|
||||
}
|
||||
|
||||
const initialState: AuthState = {
|
||||
accessToken: null,
|
||||
currentUser: null
|
||||
}
|
||||
|
||||
export const authSlice = createSlice({
|
||||
name: 'auth',
|
||||
initialState,
|
||||
reducers: {
|
||||
setUnauthed: state => {
|
||||
state.accessToken = null
|
||||
state.currentUser = null
|
||||
}
|
||||
},
|
||||
extraReducers: (builder) => {
|
||||
builder.addMatcher(
|
||||
authApiSlice.endpoints.login.matchFulfilled,
|
||||
(state, { payload }) => {
|
||||
state.accessToken = payload.token.access_token
|
||||
state.currentUser = payload.user
|
||||
}
|
||||
).addMatcher(
|
||||
usersApiSlice.endpoints.registerUser.matchFulfilled,
|
||||
(state, { payload }) => {
|
||||
state.accessToken = payload.token.access_token
|
||||
state.currentUser = payload.user
|
||||
}
|
||||
)
|
||||
},
|
||||
})
|
||||
|
||||
export const { setUnauthed } = authSlice.actions
|
||||
export default authSlice.reducer
|
||||
7
services/frontend/src/app/hooks.ts
Normal file
7
services/frontend/src/app/hooks.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import { useDispatch, useSelector } from 'react-redux'
|
||||
import type { TypedUseSelectorHook } from 'react-redux'
|
||||
|
||||
import type { RootState, AppDispatch } from './store'
|
||||
|
||||
export const useAppDispatch: () => AppDispatch = useDispatch
|
||||
export const useAppSelector: TypedUseSelectorHook<RootState> = useSelector
|
||||
16
services/frontend/src/app/store.ts
Normal file
16
services/frontend/src/app/store.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
import { configureStore } from '@reduxjs/toolkit'
|
||||
|
||||
import { apiSlice } from './features/api'
|
||||
import authReducer from './features/auth'
|
||||
|
||||
export const store = configureStore({
|
||||
reducer: {
|
||||
auth: authReducer,
|
||||
[apiSlice.reducerPath]: apiSlice.reducer,
|
||||
},
|
||||
middleware: getDefaultMiddleware =>
|
||||
getDefaultMiddleware().concat(apiSlice.middleware)
|
||||
});
|
||||
|
||||
export type RootState = ReturnType<typeof store.getState>
|
||||
export type AppDispatch = typeof store.dispatch
|
||||
19
services/frontend/src/app/types/api.ts
Normal file
19
services/frontend/src/app/types/api.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
export type RawResponse = {
|
||||
status: string;
|
||||
msg?: string;
|
||||
data?: object;
|
||||
}
|
||||
|
||||
export type ErrorResponse = RawResponse & {
|
||||
msg: string;
|
||||
data?: null;
|
||||
}
|
||||
|
||||
export type RawTimestamp = {
|
||||
seconds: number;
|
||||
nanos?: number;
|
||||
}
|
||||
|
||||
export const convertRawTimestamp = (timestamp: RawTimestamp): string => {
|
||||
return new Date(timestamp.seconds * 1000).toISOString()
|
||||
}
|
||||
27
services/frontend/src/app/types/auth.ts
Normal file
27
services/frontend/src/app/types/auth.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import { convertRawUser } from './user'
|
||||
|
||||
import type { RawUser } from './user'
|
||||
import type { RawResponse } from './api'
|
||||
import type { AuthData, AuthToken } from './common'
|
||||
|
||||
// API Request Paramaters
|
||||
export type LoginRequest = {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
// API Responses
|
||||
type RawAuthData = {
|
||||
token: AuthToken,
|
||||
user: RawUser
|
||||
}
|
||||
|
||||
export type RawAuthResponse = RawResponse & {
|
||||
data?: RawAuthData;
|
||||
}
|
||||
|
||||
// API Response Conversion
|
||||
export const convertRawAuthData = (data: RawAuthData): AuthData => ({
|
||||
token: data.token,
|
||||
user: convertRawUser(data.user)
|
||||
})
|
||||
68
services/frontend/src/app/types/comments.ts
Normal file
68
services/frontend/src/app/types/comments.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import { convertRawTimestamp } from './api';
|
||||
|
||||
import type { Comment } from './common';
|
||||
import type { RawResponse, RawTimestamp } from './api';
|
||||
|
||||
// Request Data
|
||||
export type CreateCommentData = {
|
||||
message: string;
|
||||
}
|
||||
|
||||
export type UpdateCommentData = Partial<CreateCommentData>
|
||||
|
||||
// API Request Paramaters
|
||||
export type GetPostCommentsRequest = {
|
||||
postId: string;
|
||||
}
|
||||
|
||||
type UpdateCommentRequest = {
|
||||
id: string;
|
||||
data: UpdateCommentData;
|
||||
}
|
||||
|
||||
export type UpdatePostCommentRequest = UpdateCommentRequest & {
|
||||
postId: string;
|
||||
}
|
||||
|
||||
type DeleteCommentRequest = {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type DeletePostCommentRequest = DeleteCommentRequest & {
|
||||
postId: string;
|
||||
}
|
||||
|
||||
export type CreatePostCommentRequest = {
|
||||
postId: string;
|
||||
data: CreateCommentData;
|
||||
}
|
||||
|
||||
// API Responses
|
||||
export type RawComment = {
|
||||
id: string;
|
||||
post_id: string;
|
||||
author_id: string;
|
||||
message: string;
|
||||
created_at: RawTimestamp;
|
||||
updated_at?: RawTimestamp;
|
||||
}
|
||||
|
||||
export type RawCommentResponse = RawResponse & {
|
||||
data?: RawComment;
|
||||
}
|
||||
|
||||
export type RawCommentsResponse = RawResponse & {
|
||||
data?: {
|
||||
comments: RawComment[];
|
||||
};
|
||||
}
|
||||
|
||||
// API Response Conversion
|
||||
export const convertRawComment = (rawComment: RawComment): Comment => ({
|
||||
id: rawComment.id,
|
||||
postId: rawComment.post_id,
|
||||
authorId: rawComment.author_id,
|
||||
message: rawComment.message,
|
||||
createdAt: convertRawTimestamp(rawComment.created_at),
|
||||
updatedAt: (rawComment.updated_at ? convertRawTimestamp(rawComment.updated_at) : undefined),
|
||||
})
|
||||
50
services/frontend/src/app/types/common.ts
Normal file
50
services/frontend/src/app/types/common.ts
Normal file
@@ -0,0 +1,50 @@
|
||||
// Auth
|
||||
export type AuthData = {
|
||||
token: AuthToken;
|
||||
user: User;
|
||||
}
|
||||
|
||||
export type AuthToken = {
|
||||
token_type: string;
|
||||
access_token: string;
|
||||
expires_in: number;
|
||||
}
|
||||
|
||||
// Panel
|
||||
export type Panel = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
createdAt: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
// Post
|
||||
export type Post = {
|
||||
id: string;
|
||||
panelId: string;
|
||||
authorId: string;
|
||||
title: string;
|
||||
content: string;
|
||||
createdAt: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
// Comment
|
||||
export type Comment = {
|
||||
id: string;
|
||||
postId: string;
|
||||
authorId: string;
|
||||
message: string;
|
||||
createdAt: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
|
||||
// User
|
||||
export type User = {
|
||||
id: string;
|
||||
username: string;
|
||||
isAdmin: boolean;
|
||||
createdAt?: string;
|
||||
updatedAt?: string;
|
||||
}
|
||||
59
services/frontend/src/app/types/panels.ts
Normal file
59
services/frontend/src/app/types/panels.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import { convertRawTimestamp } from './api';
|
||||
|
||||
import type { Panel } from './common';
|
||||
import type { RawResponse, RawTimestamp } from './api';
|
||||
|
||||
// Request Data
|
||||
export type CreatePanelData = {
|
||||
name: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
export type UpdatePanelData = Partial<CreatePanelData>
|
||||
|
||||
// API Request Paramaters
|
||||
type PanelByIdBase = {
|
||||
id: string;
|
||||
}
|
||||
|
||||
type PanelByNameBase = {
|
||||
name: string;
|
||||
}
|
||||
|
||||
export type GetPanelByIdRequest = PanelByIdBase;
|
||||
export type GetPanelByNameRequest = PanelByNameBase;
|
||||
|
||||
export type UpdatePanelByIdRequest = PanelByIdBase & {
|
||||
data: UpdatePanelData;
|
||||
}
|
||||
|
||||
export type UpdatePanelByNameRequest = PanelByNameBase & {
|
||||
data: UpdatePanelData;
|
||||
}
|
||||
|
||||
export type DeletePanelByIdRequest = PanelByIdBase;
|
||||
export type DeletePanelByNameRequest = PanelByNameBase;
|
||||
|
||||
export type CreatePanelRequest = CreatePanelData;
|
||||
|
||||
// API Responses
|
||||
export type RawPanel = {
|
||||
id: string;
|
||||
name: string;
|
||||
description: string;
|
||||
created_at: RawTimestamp;
|
||||
updated_at?: RawTimestamp;
|
||||
}
|
||||
|
||||
export type RawPanelResponse = RawResponse & {
|
||||
data?: RawPanel;
|
||||
}
|
||||
|
||||
// API Response Conversion
|
||||
export const convertRawPanel = (rawPanel: RawPanel): Panel => ({
|
||||
id: rawPanel.id,
|
||||
name: rawPanel.name,
|
||||
description: rawPanel.description,
|
||||
createdAt: convertRawTimestamp(rawPanel.created_at),
|
||||
updatedAt: (rawPanel.updated_at ? convertRawTimestamp(rawPanel.updated_at) : undefined),
|
||||
})
|
||||
72
services/frontend/src/app/types/posts.ts
Normal file
72
services/frontend/src/app/types/posts.ts
Normal file
@@ -0,0 +1,72 @@
|
||||
import { convertRawTimestamp } from './api'
|
||||
|
||||
import type { Post } from './common'
|
||||
import type { RawResponse, RawTimestamp } from './api'
|
||||
|
||||
// Request Data
|
||||
export type CreatePostData = {
|
||||
title: string;
|
||||
content: string;
|
||||
}
|
||||
|
||||
export type UpdatePostData = Partial<CreatePostData>
|
||||
|
||||
// API Request Paramaters
|
||||
export type GetPanelPostRequest = {
|
||||
id: string;
|
||||
panelId: string;
|
||||
}
|
||||
|
||||
export type GetUserPostsRequest = {
|
||||
userId: string;
|
||||
}
|
||||
|
||||
export type GetPanelPostsRequest = {
|
||||
panelId: string;
|
||||
}
|
||||
|
||||
export type UpdatePostRequest = {
|
||||
id: string;
|
||||
data: UpdatePostData;
|
||||
}
|
||||
|
||||
export type DeletePostRequest = {
|
||||
id: string;
|
||||
}
|
||||
|
||||
export type CreatePostRequest = {
|
||||
panelId: string;
|
||||
data: CreatePostData;
|
||||
}
|
||||
|
||||
// API Responses
|
||||
export type RawPost = {
|
||||
id: string;
|
||||
panel_id: string;
|
||||
author_id: string;
|
||||
title: string;
|
||||
content: string;
|
||||
created_at: RawTimestamp;
|
||||
updated_at?: RawTimestamp;
|
||||
}
|
||||
|
||||
export type RawPostResponse = RawResponse & {
|
||||
data?: RawPost;
|
||||
}
|
||||
|
||||
export type RawPostsResponse = RawResponse & {
|
||||
data?: {
|
||||
posts: RawPost[];
|
||||
};
|
||||
}
|
||||
|
||||
// API Response Conversion
|
||||
export const convertRawPost = (rawPost: RawPost): Post => ({
|
||||
id: rawPost.id,
|
||||
panelId: rawPost.panel_id,
|
||||
authorId: rawPost.author_id,
|
||||
title: rawPost.title,
|
||||
content: rawPost.content,
|
||||
createdAt: convertRawTimestamp(rawPost.created_at),
|
||||
updatedAt: (rawPost.updated_at ? convertRawTimestamp(rawPost.updated_at) : undefined),
|
||||
})
|
||||
49
services/frontend/src/app/types/user.ts
Normal file
49
services/frontend/src/app/types/user.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { convertRawTimestamp } from './api';
|
||||
|
||||
import type { User } from './common';
|
||||
import type { RawResponse, RawTimestamp } from './api';
|
||||
|
||||
// Request Data
|
||||
type RegisterUserData = {
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
// API Request Paramaters
|
||||
type UserByIdBase = {
|
||||
id: string;
|
||||
}
|
||||
|
||||
type UserByNameBase = {
|
||||
username: string;
|
||||
}
|
||||
|
||||
export type GetUserByIdRequest = UserByIdBase
|
||||
export type GetUserByNameRequest = UserByNameBase
|
||||
|
||||
export type DeleteUserByIdRequest = UserByIdBase
|
||||
export type DeleteUserByNameRequest = UserByNameBase
|
||||
|
||||
export type RegisterUserRequest = RegisterUserData
|
||||
|
||||
// API Responses
|
||||
export type RawUser = {
|
||||
id: string;
|
||||
username: string;
|
||||
is_admin?: boolean;
|
||||
created_at?: RawTimestamp;
|
||||
updated_at?: RawTimestamp;
|
||||
}
|
||||
|
||||
export type RawUserResponse = RawResponse & {
|
||||
data?: RawUser;
|
||||
}
|
||||
|
||||
// API Response Conversion
|
||||
export const convertRawUser = (rawUser: RawUser): User => ({
|
||||
id: rawUser.id,
|
||||
username: rawUser.username,
|
||||
isAdmin: (rawUser.is_admin ? rawUser.is_admin : false),
|
||||
createdAt: (rawUser.created_at ? convertRawTimestamp(rawUser.created_at) : undefined),
|
||||
updatedAt: (rawUser.updated_at ? convertRawTimestamp(rawUser.updated_at) : undefined),
|
||||
})
|
||||
Reference in New Issue
Block a user