Add_Directory_and_ProfilePicture #9

Merged
lx merged 13 commits from Add_Directory into main 2021-08-16 14:44:53 +00:00
7 changed files with 110 additions and 117 deletions
Showing only changes of commit 01bf4aa522 - Show all commits

View file

@ -1,5 +1,5 @@
BIN=guichet
SRC=main.go ssha.go profile.go admin.go invite.go
SRC=main.go ssha.go profile.go admin.go invite.go directory.go picture.go
DOCKER=lxpz/guichet_amd64
all: $(BIN)

View file

@ -1,13 +1,12 @@
package main
import (
"encoding/json"
"html/template"
"net/http"
"sort"
"strings"
"github.com/go-ldap/ldap/v3"
"github.com/gorilla/mux"
)
const FIELD_NAME_PROFILE_PICTURE = "profilePicture"
@ -25,28 +24,34 @@ func handleDirectory(w http.ResponseWriter, r *http.Request) {
}
erwan marked this conversation as resolved Outdated
Outdated
Review

L'identifiant, c'est le CN ? Dans tous les cas Identifiant c'est un mot français, il faudrait appeller ça plutôt juste Id.

L'identifiant, c'est le CN ? Dans tous les cas `Identifiant` c'est un mot français, il faudrait appeller ça plutôt juste `Id`.
type SearchResult struct {
erwan marked this conversation as resolved Outdated
Outdated
Review

C'est le displayname que tu prends depuis le LDAP? Dans ce cas il faudrait appeller ça DisplayName

C'est le displayname que tu prends depuis le LDAP? Dans ce cas il faudrait appeller ça `DisplayName`
Id string `json:"id"`
Displayname string `json:"displayname"`
Email string `json:"email"`
Description string `json:"description"`
DN string `json:"dn"`
DN string
Id string
DisplayName string
Email string
Description string
ProfilePicture string
}
type Results struct {
Search []SearchResult `json:"search"`
MessageID uint32 `json:"id"`
type SearchResults struct {
Results []SearchResult
}
type UniqueID struct {
Id int `json:"id"`
}
func handleDirectorySearch(w http.ResponseWriter, r *http.Request) {
templateDirectoryResults := template.Must(template.ParseFiles("templates/directory_results.html"))
func handleSearch(w http.ResponseWriter, r *http.Request) {
//Get input value by user
input := mux.Vars(r)["input"]
r.ParseMultipartForm(1024)
input := strings.TrimSpace(strings.Join(r.Form["query"], ""))
if r.Method != "POST" || input == "" {
http.Error(w, "Invalid request", http.StatusBadRequest)
return
}
//Log to allow the research
login := checkLogin(w, r)
if login == nil {
http.Error(w, "Login required", http.StatusUnauthorized)
return
}
@ -71,42 +76,46 @@ func handleSearch(w http.ResponseWriter, r *http.Request) {
}
//Transform the researh's result in a correct struct to send JSON
var result Results
results := []SearchResult{}
for _, values := range sr.Entries {
if strings.Contains(values.GetAttributeValue(config.UserNameAttr), input) || strings.Contains(values.GetAttributeValue("displayname"), input) ||
(values.GetAttributeValue("email") != "" && strings.Contains(values.GetAttributeValue("email"), input)) {
result = Results{
Search: append(result.Search, SearchResult{
Id: values.GetAttributeValue(config.UserNameAttr),
Displayname: values.GetAttributeValue("displayname"),
Email: values.GetAttributeValue("email"),
Description: values.GetAttributeValue("description"),
DN: values.DN,
}),
}
}
}
if result.Search == nil {
result = Results{
Search: append(result.Search, SearchResult{}),
if ContainsI(values.GetAttributeValue(config.UserNameAttr), input) ||
ContainsI(values.GetAttributeValue("displayname"), input) ||
ContainsI(values.GetAttributeValue("mail"), input) {
results = append(results, SearchResult{
DN: values.DN,
Id: values.GetAttributeValue(config.UserNameAttr),
DisplayName: values.GetAttributeValue("displayname"),
Email: values.GetAttributeValue("mail"),
Outdated
Review
  1. Pourquoi un résultat vide?

  2. Si tu veux créer un résultat vide en Go, pas la peine de mettre tous les champs à "", si tu écris juste SearchResult{} ça crée une structure avec des chaines vides dans tous les champs

1. Pourquoi un résultat vide? 2. Si tu veux créer un résultat vide en Go, pas la peine de mettre tous les champs à `""`, si tu écris juste `SearchResult{}` ça crée une structure avec des chaines vides dans tous les champs
Outdated
Review

Un résultat vide permet d'avoir un readyStateChange dans le JS ce qui permet d'initialiser le tableau à vide quand il n'y a plus de match.

Pour le go fmt, j'utilise VsCode et à chaque sauvegarde il fait le go fmt et aussi de tous les warnings.

Un résultat vide permet d'avoir un readyStateChange dans le JS ce qui permet d'initialiser le tableau à vide quand il n'y a plus de match. Pour le `go fmt`, j'utilise VsCode et à chaque sauvegarde il fait le `go fmt` et aussi de tous les warnings.
Outdated
Review

Mais si tu met pas le SearchResult vide, tu as quand même le readyStateChange non ? Et tu as un tableau JSON vide, c'est tout, mais ta partie JS est sensé gérer ça

Mais si tu met pas le `SearchResult` vide, tu as quand même le readyStateChange non ? Et tu as un tableau JSON vide, c'est tout, mais ta partie JS est sensé gérer ça
Outdated
Review

Justement au début j'avais fait ça. Ce qui me donnait une valeur null. Mais la fonction JS suivante ne répondez pas, elle ignorait juste la réponse. (même en enlevant le if)

 xhttp.onreadystatechange = function() {
        if (this.readyState == 4 && this.status == 201) {

Elle n'était tout bonnement pas appelée.

Justement au début j'avais fait ça. Ce qui me donnait une valeur `null`. Mais la fonction JS suivante ne répondez pas, elle ignorait juste la réponse. (même en enlevant le if) ```js xhttp.onreadystatechange = function() { if (this.readyState == 4 && this.status == 201) { ``` Elle n'était tout bonnement pas appelée.
Description: values.GetAttributeValue("description"),
ProfilePicture: values.GetAttributeValue(FIELD_NAME_PROFILE_PICTURE),
})
}
}
var id UniqueID
//Decode JSON body
err = json.NewDecoder(r.Body).Decode(&id)
if err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
return
search_results := SearchResults{
Results: results,
}
result.MessageID = uint32(id.Id)
sort.Sort(&search_results)
//Send JSON through xhttp
w.Header().Set("Content-Type", "application/json; charset=UTF-8")
w.WriteHeader(http.StatusCreated)
if err := json.NewEncoder(w).Encode(result); err != nil {
http.Error(w, err.Error(), http.StatusInternalServerError)
}
templateDirectoryResults.Execute(w, search_results)
}
func ContainsI(a string, b string) bool {
return strings.Contains(
strings.ToLower(a),
strings.ToLower(b),
)
}
func (r *SearchResults) Len() int {
return len(r.Results)
}
func (r *SearchResults) Less(i, j int) bool {
return r.Results[i].Id < r.Results[j].Id
}
func (r *SearchResults) Swap(i, j int) {
r.Results[i], r.Results[j] = r.Results[j], r.Results[i]
}

View file

@ -115,8 +115,8 @@ func main() {
r.HandleFunc("/passwd", handlePasswd)
r.HandleFunc("/picture/{name}", handleDownloadPicture)
r.HandleFunc("/directory/search", handleDirectorySearch)
r.HandleFunc("/directory", handleDirectory)
r.HandleFunc("/directory/search/{input}", handleSearch)
r.HandleFunc("/invite/new_account", handleInviteNewAccount)
r.HandleFunc("/invite/send_code", handleInviteSendCode)

View file

@ -1,51 +1,24 @@
var perso_id = 0;
var last_id = 0;
function searchDirectory() {
var input = document.getElementById("search").value;
if(input){
last_id++;
var request_id = last_id;
var data = new FormData();
data.append("query", input);
var xhttp = new XMLHttpRequest();
xhttp.onreadystatechange = function() {
if (this.readyState == 4 && this.status == 201) {
// Typical action to be performed when the document is ready:
//Response from Request Ajax
var jsonResponse = JSON.parse(xhttp.responseText);
if (request_id != last_id) return;
if (last_id < jsonResponse.id) {
last_id = jsonResponse.id
//We get the old table element, we create an new table element then we increment this new table.
//After the new add, we replace the old table by the new one.
var old_table = document.getElementById("users");
var table = document.createElement('tbody');
table.setAttribute("id","users");
for (let i =0; i < Object.keys(jsonResponse.search).length; i++) {
var row = table.insertRow(0);
var urlName = row.insertCell(0);
var identifiant = row.insertCell(1);
var displayname = row.insertCell(2);
var email = row.insertCell(3);
var description = row.insertCell(4);
description.setAttribute("style", "word-break: break-all;");
if (jsonResponse.search[i].dn.localeCompare("")!=0) {
urlName.innerText = `<object data="/image/${jsonResponse.search[i].dn}/little" class=".img-thumbnail"><image src="/image/unknown_profile/little" class=".img-thumbnail"></object>`
}else {
urlName.innerText=""
}
identifiant.innerText = `<a href="/admin/ldap/${jsonResponse.search[i].dn}">${jsonResponse.search[i].id}</a>`
displayname.innerText = jsonResponse.search[i].displayname
email.innerText = jsonResponse.search[i].email
description.innerText = jsonResponse.search[i].description
}
old_table.parentNode.replaceChild(table, old_table)
if (this.readyState == 4 && this.status == 200) {
var result_div = document.getElementById("search-results");
result_div.innerHTML = xhttp.responseText;
}
}
};
perso_id += 1
xhttp.overrideMimeType("application/json");
xhttp.open("POST", "/search/".concat(input), true);
xhttp.send(JSON.stringify({"id": perso_id}));
xhttp.open("POST", "/directory/search", true);
xhttp.send(data);
}
}
}

View file

@ -1,35 +1,23 @@
{{define "title"}}Directory |{{end}}
{{define "title"}}Annuaire |{{end}}
{{define "body"}}
<div class="d-flex">
<h4>Directory</h4>
<h4>Annuaire</h4>
<a class="ml-auto btn btn-info" href="/">Menu principal</a>
</div>
<div class="d-flex">
<div class="d-flex mx-auto">
<p class="">Name:</p>
<form class="px-2" style="width: fit-content;">
<input id="search" type="text" onkeyup="searchDirectory()" size="20">
</form>
</div>
</div>
<form>
<div class="form-group form-row">
<div class="col-sm-2">&nbsp;</div>
<label for="search" class="col-sm-2 col-form-label">Rechercher :</label>
<input class="form-control col-sm-4" id="search" name="search" type="text" onkeyup="searchDirectory()" size="20">
</div>
</form>
<table class="table mt-4">
<thead>
<th scope="col">Profil image</th>
<th scope="col">Identifiant</th>
<th scope="col">Nom complet</th>
<th scope="col">Email</th>
<th scope="col">Description</th>
</thead>
<tbody id="users">
</tbody>
</table>
<script src="/static/javascript/search.js"></script>
<div id="search-results"></div>
{{end}}
<script src="/static/javascript/search.js"></script>
{{end}}

View file

@ -0,0 +1,23 @@
{{if .Results}}
{{range .Results}}
<div class="card mt-4">
<div class="card-body">
<div class="float-right">
{{if .ProfilePicture}}
<a href="/picture/{{.ProfilePicture}}">
<img src="/picture/{{.ProfilePicture}}-thumb"/>
</a>
{{else}}
{{end}}
</div>
<h5 class="card-title">
{{.DisplayName}}
<code>{{.Id}}@</code>
</h5>
<p class="card-text">{{.Description}}</p>
</div>
</div>
{{end}}
{{else}}
Aucun résultat.
{{end}}

View file

@ -5,7 +5,7 @@
<h4>Modifier mon profil</h4>
<a class="ml-auto btn btn-info" href="/">Retour</a>
</div>
<h5>Photo de profil</h5>
{{if .ErrorMessage}}
<div class="alert alert-danger mt-4">Impossible d'effectuer la modification.
<div style="font-size: 0.8em">{{ .ErrorMessage }}</div>
@ -70,8 +70,8 @@
</div>
<div class="form-group">
<label for="description">Description (180 caractères maximum)</label>
<textarea id="description" name="description" class="form-control" maxlength="180">{{ .Description }}</textarea>
<label for="description">Description</label>
<textarea id="description" name="description" class="form-control">{{ .Description }}</textarea>
</div>
<button type="submit" class="btn btn-primary">Enregistrer les modifications</button>
</form>