diff --git a/server/api/api.go b/server/api/api.go index 5f070d74d..d9d83b476 100644 --- a/server/api/api.go +++ b/server/api/api.go @@ -10,6 +10,7 @@ import ( "path/filepath" "strconv" "strings" + "time" "github.com/gorilla/mux" "github.com/mattermost/mattermost-octo-tasks/server/app" @@ -34,18 +35,21 @@ func (a *API) app() *app.App { func (a *API) RegisterRoutes(r *mux.Router) { r.HandleFunc("/api/v1/blocks", a.sessionRequired(a.handleGetBlocks)).Methods("GET") - r.HandleFunc("/api/v1/blocks", a.handlePostBlocks).Methods("POST") - r.HandleFunc("/api/v1/blocks/{blockID}", a.handleDeleteBlock).Methods("DELETE") - r.HandleFunc("/api/v1/blocks/{blockID}/subtree", a.handleGetSubTree).Methods("GET") + r.HandleFunc("/api/v1/blocks", a.sessionRequired(a.handlePostBlocks)).Methods("POST") + r.HandleFunc("/api/v1/blocks/{blockID}", a.sessionRequired(a.handleDeleteBlock)).Methods("DELETE") + r.HandleFunc("/api/v1/blocks/{blockID}/subtree", a.sessionRequired(a.handleGetSubTree)).Methods("GET") + + r.HandleFunc("/api/v1/users/me", a.sessionRequired(a.handleGetMe)).Methods("GET") + r.HandleFunc("/api/v1/users/{userID}", a.sessionRequired(a.handleGetUser)).Methods("GET") r.HandleFunc("/api/v1/login", a.handleLogin).Methods("POST") r.HandleFunc("/api/v1/register", a.handleRegister).Methods("POST") - r.HandleFunc("/api/v1/files", a.handleUploadFile).Methods("POST") - r.HandleFunc("/files/{filename}", a.handleServeFile).Methods("GET") + r.HandleFunc("/api/v1/files", a.sessionRequired(a.handleUploadFile)).Methods("POST") + r.HandleFunc("/files/{filename}", a.sessionRequired(a.handleServeFile)).Methods("GET") - r.HandleFunc("/api/v1/blocks/export", a.handleExport).Methods("GET") - r.HandleFunc("/api/v1/blocks/import", a.handleImport).Methods("POST") + r.HandleFunc("/api/v1/blocks/export", a.sessionRequired(a.handleExport)).Methods("GET") + r.HandleFunc("/api/v1/blocks/import", a.sessionRequired(a.handleImport)).Methods("POST") } func (a *API) handleGetBlocks(w http.ResponseWriter, r *http.Request) { @@ -137,6 +141,65 @@ func (a *API) handlePostBlocks(w http.ResponseWriter, r *http.Request) { jsonStringResponse(w, http.StatusOK, "{}") } +func (a *API) handleGetUser(w http.ResponseWriter, r *http.Request) { + vars := mux.Vars(r) + userID := vars["userID"] + + user, err := a.app().GetUser(userID) + if err != nil { + log.Printf(`ERROR: %v`, r) + errorResponse(w, http.StatusInternalServerError, nil) + + return + } + + userData, err := json.Marshal(user) + if err != nil { + log.Printf(`ERROR: %v`, r) + errorResponse(w, http.StatusInternalServerError, nil) + + return + } + + jsonStringResponse(w, http.StatusOK, string(userData)) +} + +func (a *API) handleGetMe(w http.ResponseWriter, r *http.Request) { + ctx := r.Context() + session := ctx.Value("session").(*model.Session) + var user *model.User + var err error + + if session.UserID == "single-user" { + now := time.Now().Unix() + user = &model.User{ + ID: "single-user", + Username: "single-user", + Email: "single-user", + CreateAt: now, + UpdateAt: now, + } + } else { + user, err = a.app().GetUser(session.UserID) + if err != nil { + log.Printf(`ERROR: %v`, r) + errorResponse(w, http.StatusInternalServerError, nil) + + return + } + } + + userData, err := json.Marshal(user) + if err != nil { + log.Printf(`ERROR: %v`, r) + errorResponse(w, http.StatusInternalServerError, nil) + + return + } + + jsonStringResponse(w, http.StatusOK, string(userData)) +} + func (a *API) handleDeleteBlock(w http.ResponseWriter, r *http.Request) { vars := mux.Vars(r) blockID := vars["blockID"] diff --git a/server/app/auth.go b/server/app/auth.go index f44f87ed0..3e5dcd1c2 100644 --- a/server/app/auth.go +++ b/server/app/auth.go @@ -23,6 +23,15 @@ func (a *App) GetSession(token string) (*model.Session, error) { return session, nil } +// GetUser Get an existing active user by id +func (a *App) GetUser(ID string) (*model.User, error) { + user, err := a.store.GetUserById(ID) + if err != nil { + return nil, errors.Wrap(err, "unable to get the session for the token") + } + return user, nil +} + // Login create a new user session if the authentication data is valid func (a *App) Login(username string, email string, password string, mfaToken string) (string, error) { var user *model.User diff --git a/server/services/store/sqlstore/user.go b/server/services/store/sqlstore/user.go index 5b82e10b4..48bb56e07 100644 --- a/server/services/store/sqlstore/user.go +++ b/server/services/store/sqlstore/user.go @@ -13,6 +13,7 @@ func (s *SQLStore) getUserByCondition(condition sq.Eq) (*model.User, error) { query := s.getQueryBuilder(). Select("id", "username", "email", "password", "mfa_secret", "auth_service", "auth_data", "props", "create_at", "update_at", "delete_at"). From("users"). + Where(sq.Eq{"delete_at": 0}). Where(condition) row := query.QueryRow() user := model.User{} diff --git a/webapp/src/app.tsx b/webapp/src/app.tsx index 27f4c86cd..e01f895a1 100644 --- a/webapp/src/app.tsx +++ b/webapp/src/app.tsx @@ -1,6 +1,6 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. -import React, {useState} from 'react' +import React from 'react' import {IntlProvider} from 'react-intl' import { @@ -11,6 +11,7 @@ import { } from 'react-router-dom' import client from './octoClient' +import {IUser, UserContext} from './user' import {getCurrentLanguage, getMessages, storeLanguage} from './i18n' @@ -20,40 +21,65 @@ import LoginPage from './pages/loginPage' import RegisterPage from './pages/registerPage' import BoardPage from './pages/boardPage' -export default function App(): JSX.Element { - const [language, setLanguage] = useState(getCurrentLanguage()) - const setAndStoreLanguage = (lang: string) => { - storeLanguage(lang) - setLanguage(lang) - } - return ( - - - - - - - - - - - - - - {!client.token && } - - - - {!client.token && } - - - - - - - - ) +type State = { + language: string, + user: IUser|null, + initialLoad: boolean, +} + +export default class App extends React.PureComponent { + constructor(props: unknown) { + super(props) + this.state = { + language: getCurrentLanguage(), + user: null, + initialLoad: false, + } + } + + public componentDidMount(): void { + client.getMe().then((user: IUser|null) => { + this.setState({user, initialLoad: true}) + }) + } + + setAndStoreLanguage = (lang: string): void => { + storeLanguage(lang) + this.setState({language: lang}) + } + + public render(): JSX.Element { + return ( + + + + + + + + + + + + + + + {this.state.initialLoad && !this.state.user && } + + + + {this.state.initialLoad && !this.state.user && } + + + + + + + + + ) + } } diff --git a/webapp/src/octoClient.ts b/webapp/src/octoClient.ts index c394d0ea0..8d6a18f31 100644 --- a/webapp/src/octoClient.ts +++ b/webapp/src/octoClient.ts @@ -1,6 +1,7 @@ // Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. // See LICENSE.txt for license information. import {IBlock, IMutableBlock} from './blocks/block' +import {IUser} from './user' import {Utils} from './utils' // @@ -58,6 +59,13 @@ class OctoClient { } } + async getMe(): Promise { + const path = '/api/v1/users/me' + const response = await fetch(this.serverUrl + path, {headers: this.headers()}) + const user = (await response.json()) as IUser || null + return user + } + async getSubtree(rootId?: string, levels = 2): Promise { const path = `/api/v1/blocks/${rootId}/subtree?l=${levels}` const response = await fetch(this.serverUrl + path, {headers: this.headers()}) diff --git a/webapp/src/user.tsx b/webapp/src/user.tsx new file mode 100644 index 000000000..4b80a1a2a --- /dev/null +++ b/webapp/src/user.tsx @@ -0,0 +1,17 @@ +// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved. +// See LICENSE.txt for license information. + +import React from 'react' + +const UserContext = React.createContext(null as IUser|null) + +interface IUser { + id: string, + username: string, + email: string, + props: Record, + createAt: number, + updateAt: number, +} + +export {IUser, UserContext}