Add separate session endpoint for additional non-user data
This is conceptually much cleaner that encumbering the user object.
This commit is contained in:
parent
00ebc27069
commit
42d111aac9
16 changed files with 166 additions and 177 deletions
|
|
@ -38,6 +38,8 @@ func AddRoutes(r *mux.Router) {
|
|||
review_task := resource.ReviewTask(r)
|
||||
r.Handle("/review-task", authenticatedHandlerJSON(review_task.List)).Methods("GET")
|
||||
r.Handle("/service-request", auth.NewEnsureAuth(apiServiceRequest)).Methods("GET")
|
||||
session := resource.Session(router)
|
||||
r.Handle("/session", authenticatedHandlerJSON(session.Get)).Methods("GET").Name("session.get")
|
||||
signal := resource.Signal(r)
|
||||
r.Handle("/signal", authenticatedHandlerJSON(signal.List)).Methods("GET")
|
||||
r.Handle("/sudo/email", authenticatedHandlerJSONPost(postSudoEmail)).Methods("POST")
|
||||
|
|
|
|||
|
|
@ -27,10 +27,10 @@ type Notification struct {
|
|||
Time time.Time
|
||||
Type string
|
||||
}
|
||||
type UserNotificationCounts struct {
|
||||
Communications uint `json:"communication"`
|
||||
Home uint `json:"home"`
|
||||
Review uint `json:"review"`
|
||||
type notificationCounts struct {
|
||||
Communications uint
|
||||
Home uint
|
||||
Review uint
|
||||
}
|
||||
|
||||
// Clear all notifications for a given user with the given path
|
||||
|
|
@ -105,7 +105,7 @@ func NotificationsForUser(ctx context.Context, u User) ([]Notification, error) {
|
|||
}
|
||||
return results, nil
|
||||
}
|
||||
func NotificationCountsForUser(ctx context.Context, u User) (*UserNotificationCounts, error) {
|
||||
func NotificationCountsForUser(ctx context.Context, u User) (*notificationCounts, error) {
|
||||
count_home, err := u.model.UserNotifications(
|
||||
models.SelectWhere.Notifications.ResolvedAt.IsNull(),
|
||||
).Count(ctx, db.PGInstance.BobDB)
|
||||
|
|
@ -125,7 +125,7 @@ func NotificationCountsForUser(ctx context.Context, u User) (*UserNotificationCo
|
|||
return nil, fmt.Errorf("Failed to get review notification count: %w", err)
|
||||
}
|
||||
//log.Debug().Int64("reports", count_reports).Int64("home", count_home).Int64("review", count_review).Int("user", u.ID).Msg("calculated notification counts")
|
||||
return &UserNotificationCounts{
|
||||
return ¬ificationCounts{
|
||||
Communications: uint(count_reports),
|
||||
Home: uint(count_home),
|
||||
Review: uint(count_review),
|
||||
|
|
|
|||
|
|
@ -8,12 +8,13 @@ import (
|
|||
"github.com/Gleipnir-Technology/bob/dialect/psql/sm"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform/types"
|
||||
//"github.com/google/uuid"
|
||||
)
|
||||
|
||||
type Organization struct {
|
||||
ID int32 `json:"id"`
|
||||
ServiceArea *ServiceArea `json:"service_area"`
|
||||
ID int32 `json:"id"`
|
||||
ServiceArea *types.ServiceArea `json:"service_area"`
|
||||
|
||||
model *models.Organization
|
||||
}
|
||||
|
|
@ -75,11 +76,6 @@ func (o Organization) FieldseekerSyncLatest(ctx context.Context) (*models.Fields
|
|||
return sync, nil
|
||||
}
|
||||
|
||||
type ServiceArea struct {
|
||||
Min Point `json:"min"`
|
||||
Max Point `json:"max"`
|
||||
}
|
||||
|
||||
func (o Organization) ServiceRequestRecent(ctx context.Context) ([]*models.FieldseekerServicerequest, error) {
|
||||
results, err := o.model.Servicerequests(sm.OrderBy("creationdate").Desc(), sm.Limit(10)).All(ctx, db.PGInstance.BobDB)
|
||||
if err != nil {
|
||||
|
|
@ -114,19 +110,19 @@ func OrganizationList(ctx context.Context, user User) ([]*Organization, error) {
|
|||
return results, err
|
||||
}
|
||||
func newOrganization(org *models.Organization) Organization {
|
||||
var sa *ServiceArea
|
||||
var sa *types.ServiceArea
|
||||
if org.ServiceAreaXmax.IsValue() &&
|
||||
org.ServiceAreaXmin.IsValue() &&
|
||||
org.ServiceAreaYmax.IsValue() &&
|
||||
org.ServiceAreaYmin.IsValue() {
|
||||
sa = &ServiceArea{
|
||||
Min: Point{
|
||||
X: org.ServiceAreaXmin.MustGet(),
|
||||
Y: org.ServiceAreaYmin.MustGet(),
|
||||
sa = &types.ServiceArea{
|
||||
Min: types.Location{
|
||||
Longitude: org.ServiceAreaXmin.MustGet(),
|
||||
Latitude: org.ServiceAreaYmin.MustGet(),
|
||||
},
|
||||
Max: Point{
|
||||
X: org.ServiceAreaXmax.MustGet(),
|
||||
Y: org.ServiceAreaYmax.MustGet(),
|
||||
Max: types.Location{
|
||||
Longitude: org.ServiceAreaXmax.MustGet(),
|
||||
Latitude: org.ServiceAreaYmax.MustGet(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,19 +25,17 @@ type NoUserError struct{}
|
|||
func (e NoUserError) Error() string { return "That user does not exist" }
|
||||
|
||||
type User struct {
|
||||
Active bool
|
||||
Avatar *uuid.UUID
|
||||
DisplayName string
|
||||
ID int
|
||||
Initials string
|
||||
Notifications []Notification
|
||||
NotificationCounts UserNotificationCounts
|
||||
Organization Organization
|
||||
PasswordHash string
|
||||
PasswordHashType string
|
||||
Role string
|
||||
Tags []string
|
||||
Username string
|
||||
Active bool
|
||||
Avatar *uuid.UUID
|
||||
DisplayName string
|
||||
ID int
|
||||
Initials string
|
||||
Organization Organization
|
||||
PasswordHash string
|
||||
PasswordHashType string
|
||||
Role string
|
||||
Tags []string
|
||||
Username string
|
||||
|
||||
model *models.User
|
||||
}
|
||||
|
|
@ -55,27 +53,20 @@ func (u User) HasRoot() bool {
|
|||
func newUser(ctx context.Context, org Organization, user *models.User) User {
|
||||
avatar := user.Avatar.Ptr()
|
||||
u := User{
|
||||
Active: true,
|
||||
Avatar: avatar,
|
||||
DisplayName: user.DisplayName,
|
||||
ID: int(user.ID),
|
||||
Initials: extractInitials(user.DisplayName),
|
||||
Notifications: []Notification{},
|
||||
NotificationCounts: UserNotificationCounts{},
|
||||
Organization: org,
|
||||
PasswordHash: user.PasswordHash,
|
||||
PasswordHashType: string(user.PasswordHashType),
|
||||
Role: user.Role.String(),
|
||||
Tags: []string{},
|
||||
Username: user.Username,
|
||||
Active: true,
|
||||
Avatar: avatar,
|
||||
DisplayName: user.DisplayName,
|
||||
ID: int(user.ID),
|
||||
Initials: extractInitials(user.DisplayName),
|
||||
Organization: org,
|
||||
PasswordHash: user.PasswordHash,
|
||||
PasswordHashType: string(user.PasswordHashType),
|
||||
Role: user.Role.String(),
|
||||
Tags: []string{},
|
||||
Username: user.Username,
|
||||
|
||||
model: user,
|
||||
}
|
||||
counts, err := NotificationCountsForUser(ctx, u)
|
||||
if err != nil {
|
||||
log.Error().Err(err).Int32("id", user.ID).Msg("failed to get notification counts for user")
|
||||
}
|
||||
u.NotificationCounts = *counts
|
||||
return u
|
||||
}
|
||||
|
||||
|
|
@ -272,16 +263,14 @@ func extractInitials(name string) string {
|
|||
}
|
||||
func toUser(user *models.User) User {
|
||||
return User{
|
||||
DisplayName: user.DisplayName,
|
||||
ID: int(user.ID),
|
||||
Initials: extractInitials(user.DisplayName),
|
||||
Notifications: []Notification{},
|
||||
NotificationCounts: UserNotificationCounts{},
|
||||
Organization: Organization{},
|
||||
PasswordHash: user.PasswordHash,
|
||||
PasswordHashType: string(user.PasswordHashType),
|
||||
Role: user.Role.String(),
|
||||
Username: user.Username,
|
||||
DisplayName: user.DisplayName,
|
||||
ID: int(user.ID),
|
||||
Initials: extractInitials(user.DisplayName),
|
||||
Organization: Organization{},
|
||||
PasswordHash: user.PasswordHash,
|
||||
PasswordHashType: string(user.PasswordHashType),
|
||||
Role: user.Role.String(),
|
||||
Username: user.Username,
|
||||
|
||||
model: user,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,9 +6,7 @@ import (
|
|||
"net/http"
|
||||
"strconv"
|
||||
|
||||
"github.com/Gleipnir-Technology/nidus-sync/config"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/db/models"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/html"
|
||||
nhttp "github.com/Gleipnir-Technology/nidus-sync/http"
|
||||
"github.com/Gleipnir-Technology/nidus-sync/platform"
|
||||
"github.com/aarondl/opt/omitnull"
|
||||
|
|
@ -16,14 +14,12 @@ import (
|
|||
"github.com/rs/zerolog/log"
|
||||
)
|
||||
|
||||
type userResponse struct {
|
||||
Avatar *string `json:"avatar"`
|
||||
DisplayName string `json:"display_name"`
|
||||
Initials string `json:"initials"`
|
||||
IsActive bool `json:"is_active"`
|
||||
//Notifications []Notification `json:"notifications"`
|
||||
//NotificationCounts UserNotificationCounts `json:"notification_counts"`
|
||||
//Organization Organization `json:"organization"`
|
||||
type user struct {
|
||||
Avatar *string `json:"avatar"`
|
||||
DisplayName string `json:"display_name"`
|
||||
ID int `json:"id"`
|
||||
Initials string `json:"initials"`
|
||||
IsActive bool `json:"is_active"`
|
||||
PasswordHash string `json:"-"`
|
||||
PasswordHashType string `json:"-"`
|
||||
Role string `json:"role"`
|
||||
|
|
@ -37,7 +33,7 @@ func User(r *router) *userR {
|
|||
router: r,
|
||||
}
|
||||
}
|
||||
func (res *userR) response(u *platform.User) (*userResponse, error) {
|
||||
func (res *userR) response(u *platform.User) (*user, error) {
|
||||
if u == nil {
|
||||
return nil, fmt.Errorf("nil user")
|
||||
}
|
||||
|
|
@ -49,9 +45,10 @@ func (res *userR) response(u *platform.User) (*userResponse, error) {
|
|||
if err != nil {
|
||||
return nil, fmt.Errorf("id to uri: %w", err)
|
||||
}
|
||||
return &userResponse{
|
||||
return &user{
|
||||
Avatar: avatar,
|
||||
DisplayName: u.DisplayName,
|
||||
ID: int(u.ID),
|
||||
Initials: u.Initials,
|
||||
IsActive: u.Active,
|
||||
Role: u.Role,
|
||||
|
|
@ -67,24 +64,6 @@ type userR struct {
|
|||
type responseListUser struct {
|
||||
Users []*platform.User `json:"users"`
|
||||
}
|
||||
type contentURLAPI struct {
|
||||
Avatar string `json:"avatar"`
|
||||
Communication string `json:"communication"`
|
||||
PublicreportMessage string `json:"publicreport_message"`
|
||||
ReviewTask string `json:"review_task"`
|
||||
Signal string `json:"signal"`
|
||||
Upload string `json:"upload"`
|
||||
User string `json:"user"`
|
||||
}
|
||||
type contentURLs struct {
|
||||
API contentURLAPI `json:"api"`
|
||||
Tegola string `json:"tegola"`
|
||||
Tile string `json:"tile"`
|
||||
}
|
||||
type contentUserSelf struct {
|
||||
Self platform.User `json:"self"`
|
||||
URLs contentURLs `json:"urls"`
|
||||
}
|
||||
|
||||
func (res *userR) ByIDGet(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (*platform.User, *nhttp.ErrorWithStatus) {
|
||||
vars := mux.Vars(r)
|
||||
|
|
@ -97,8 +76,7 @@ func (res *userR) ByIDGet(ctx context.Context, r *http.Request, user platform.Us
|
|||
return u, nil
|
||||
}
|
||||
|
||||
func (res *userR) ByIDPut(ctx context.Context, r *http.Request, user platform.User, updates contentURLAPI) (string, *nhttp.ErrorWithStatus) {
|
||||
log.Info().Str("avatar", updates.Avatar).Msg("doing updates")
|
||||
func (res *userR) ByIDPut(ctx context.Context, r *http.Request, user platform.User, updates user) (string, *nhttp.ErrorWithStatus) {
|
||||
vars := mux.Vars(r)
|
||||
user_id_str := vars["id"]
|
||||
user_id, err := strconv.Atoi(user_id_str)
|
||||
|
|
@ -106,8 +84,8 @@ func (res *userR) ByIDPut(ctx context.Context, r *http.Request, user platform.Us
|
|||
return "", nhttp.NewErrorStatus(http.StatusBadRequest, "user update: %w", err)
|
||||
}
|
||||
user_changes := &models.UserSetter{}
|
||||
if updates.Avatar != "" {
|
||||
avatar_uuid, err := res.router.UUIDFromURI("avatar.ByUUIDGet", updates.Avatar)
|
||||
if updates.Avatar != nil {
|
||||
avatar_uuid, err := res.router.UUIDFromURI("avatar.ByUUIDGet", *updates.Avatar)
|
||||
if err != nil {
|
||||
return "", nhttp.NewBadRequest("parse avatar uri: %w", err)
|
||||
}
|
||||
|
|
@ -120,42 +98,20 @@ func (res *userR) ByIDPut(ctx context.Context, r *http.Request, user platform.Us
|
|||
return "", nil
|
||||
}
|
||||
|
||||
func (res *userR) SelfGet(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (*contentUserSelf, *nhttp.ErrorWithStatus) {
|
||||
counts, err := platform.NotificationCountsForUser(ctx, user)
|
||||
func (res *userR) SelfGet(ctx context.Context, r *http.Request, user platform.User, query QueryParams) (*user, *nhttp.ErrorWithStatus) {
|
||||
resp, err := res.response(&user)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("get notifications: %w", err)
|
||||
return nil, nhttp.NewError("create response: %w", err)
|
||||
}
|
||||
org, err := platform.OrganizationByID(ctx, int(user.Organization.ID))
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("get org: %w", err)
|
||||
}
|
||||
user.Organization = *org
|
||||
user.NotificationCounts = *counts
|
||||
urls := html.NewContentURL()
|
||||
return &contentUserSelf{
|
||||
Self: user,
|
||||
URLs: contentURLs{
|
||||
API: contentURLAPI{
|
||||
Avatar: config.MakeURLNidus("/api/avatar"),
|
||||
Communication: urls.API.Communication,
|
||||
PublicreportMessage: urls.API.Publicreport.Message,
|
||||
ReviewTask: config.MakeURLNidus("/api/review-task"),
|
||||
Signal: config.MakeURLNidus("/api/signal"),
|
||||
Upload: config.MakeURLNidus("/api/upload"),
|
||||
User: config.MakeURLNidus("/api/user"),
|
||||
},
|
||||
Tegola: urls.Tegola,
|
||||
Tile: config.MakeURLNidus("/api/tile/{z}/{y}/{x}"),
|
||||
},
|
||||
}, nil
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
func (res *userR) List(ctx context.Context, r *http.Request, user platform.User, query QueryParams) ([]*userResponse, *nhttp.ErrorWithStatus) {
|
||||
users, err := platform.UserList(ctx, user)
|
||||
func (res *userR) List(ctx context.Context, r *http.Request, u platform.User, query QueryParams) ([]*user, *nhttp.ErrorWithStatus) {
|
||||
users, err := platform.UserList(ctx, u)
|
||||
if err != nil {
|
||||
return nil, nhttp.NewError("list users: %w", err)
|
||||
}
|
||||
results := make([]*userResponse, len(users))
|
||||
results := make([]*user, len(users))
|
||||
log.Debug().Int("len", len(users)).Msg("building response")
|
||||
for i, v := range users {
|
||||
log.Debug().Int("i", i).Msg("making results")
|
||||
|
|
|
|||
|
|
@ -33,7 +33,10 @@
|
|||
<div class="card shadow-sm mb-3">
|
||||
<div class="card-header bg-white pane-header">Communication Workbench</div>
|
||||
<div class="card-body">
|
||||
<div v-if="loading || session.user == null" class="loading">
|
||||
<div
|
||||
v-if="loading || session.self == null || session.organization == null"
|
||||
class="loading"
|
||||
>
|
||||
Loading...
|
||||
</div>
|
||||
<div v-else>
|
||||
|
|
@ -42,7 +45,7 @@
|
|||
id="map"
|
||||
:bounds="mapBounds"
|
||||
:markers="mapMarkers"
|
||||
:organizationId="session.user?.organization.id"
|
||||
:organizationId="session.organization?.id"
|
||||
:tegola="session.urls?.tegola ?? ''"
|
||||
/>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -1,10 +1,10 @@
|
|||
<template>
|
||||
<p>A flyover pool</p>
|
||||
<div v-if="session.user">
|
||||
<div v-if="session.organization">
|
||||
<MapProxiedArcgisTile
|
||||
:location="location"
|
||||
:markers="markers"
|
||||
:organizationId="session.user?.organization.id"
|
||||
:organizationId="session.organization.id"
|
||||
:tegola="session.urls?.tegola ?? ''"
|
||||
:urlTiles="session.urls?.tile ?? ''"
|
||||
/>
|
||||
|
|
|
|||
|
|
@ -10,12 +10,12 @@
|
|||
Active Investigation Workbench
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="map-container" v-if="session.user">
|
||||
<div class="map-container" v-if="session.organization">
|
||||
<MapMultipoint
|
||||
id="map"
|
||||
:bounds="session.user.organization.service_area"
|
||||
:bounds="session.organization.service_area"
|
||||
:markers="markers"
|
||||
:organizationId="session.user.organization.id"
|
||||
:organizationId="session.organization.id"
|
||||
:tegola="session.urls?.tegola ?? ''"
|
||||
></MapMultipoint>
|
||||
</div>
|
||||
|
|
@ -56,7 +56,7 @@
|
|||
<MapProxiedArcgisTile
|
||||
:location="selectedSignalLocation()"
|
||||
:markers="[]"
|
||||
:organizationId="session.user?.organization.id ?? 0"
|
||||
:organizationId="session.organization?.id ?? 0"
|
||||
:tegola="session.urls?.tegola ?? ''"
|
||||
:urlTiles="session.urls?.tile ?? ''"
|
||||
@map-click="updateSignalLocation"
|
||||
|
|
|
|||
|
|
@ -115,13 +115,13 @@
|
|||
</div>
|
||||
|
||||
<!-- Map Components -->
|
||||
<div class="map-container" v-if="session.user">
|
||||
<div class="map-container" v-if="session.organization">
|
||||
<MapMultipoint
|
||||
ref="mapMultipoint"
|
||||
id="map"
|
||||
:bounds="mapBounds"
|
||||
:markers="mapMarkers"
|
||||
:organizationId="session.user.organization.id"
|
||||
:organizationId="session.organization.id"
|
||||
:tegola="session.urls?.tegola ?? ''"
|
||||
></MapMultipoint>
|
||||
</div>
|
||||
|
|
@ -129,11 +129,11 @@
|
|||
<p>loading...</p>
|
||||
</div>
|
||||
|
||||
<div class="map-container" v-if="session.user && selectedTask.pool">
|
||||
<div class="map-container" v-if="session.organization && selectedTask.pool">
|
||||
<MapProxiedArcgisTile
|
||||
:location="selectedTask.pool?.location"
|
||||
:markers="[]"
|
||||
:organizationId="session.user?.organization.id"
|
||||
:organizationId="session.organization.id"
|
||||
:tegola="session.urls?.tegola ?? ''"
|
||||
:urlTiles="session.urls?.tile ?? ''"
|
||||
@map-click="doPoolLocation"
|
||||
|
|
|
|||
|
|
@ -26,9 +26,7 @@
|
|||
to="/_/communication"
|
||||
icon="messaging"
|
||||
label="Communication"
|
||||
:notificationCount="
|
||||
session.user?.notification_counts.communication ?? 0
|
||||
"
|
||||
:notificationCount="session.notification_counts?.communication ?? 0"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
|
|
@ -42,7 +40,7 @@
|
|||
to="/_/review"
|
||||
icon="review"
|
||||
label="Review"
|
||||
:notificationCount="session.user?.notification_counts.review ?? 0"
|
||||
:notificationCount="session.notification_counts?.review ?? 0"
|
||||
/>
|
||||
</li>
|
||||
<li>
|
||||
|
|
|
|||
|
|
@ -3,17 +3,21 @@ import { ref } from "vue";
|
|||
import { SSEManager } from "@/SSEManager";
|
||||
import {
|
||||
Organization,
|
||||
Session,
|
||||
SessionNotificationCounts,
|
||||
URLs,
|
||||
User,
|
||||
UserNotificationCounts,
|
||||
UserResponse,
|
||||
} from "@/types";
|
||||
|
||||
export const useSessionStore = defineStore("session", () => {
|
||||
// State
|
||||
const error = ref<string | null>(null);
|
||||
const loading = ref(false);
|
||||
const user = ref<User | null>(null);
|
||||
const current = ref<Session | null>(null);
|
||||
const notification_counts = ref<SessionNotificationCounts | null>(null);
|
||||
const ongoingFetch = ref<Promise<Session> | null>(null);
|
||||
const organization = ref<Organization | null>(null);
|
||||
const self = ref<User | null>(null);
|
||||
const urls = ref<URLs | null>(null);
|
||||
|
||||
// Subscription
|
||||
|
|
@ -24,16 +28,18 @@ export const useSessionStore = defineStore("session", () => {
|
|||
});
|
||||
|
||||
// Actions
|
||||
async function fetchSession(): Promise<UserResponse> {
|
||||
async function fetchSession(): Promise<Session> {
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
|
||||
try {
|
||||
const response = await fetch("/api/user/self");
|
||||
const response = await fetch("/api/session");
|
||||
if (!response.ok) throw new Error("Failed to fetch user");
|
||||
|
||||
const data: UserResponse = await response.json();
|
||||
user.value = data.self;
|
||||
const data: Session = await response.json();
|
||||
notification_counts.value = data.notification_counts;
|
||||
organization.value = data.organization;
|
||||
self.value = data.self;
|
||||
urls.value = data.urls;
|
||||
return data;
|
||||
} catch (e) {
|
||||
|
|
@ -49,14 +55,33 @@ export const useSessionStore = defineStore("session", () => {
|
|||
console.log("pretend check user auth");
|
||||
return true;
|
||||
}
|
||||
|
||||
async function get(): Promise<Session> {
|
||||
if (current.value != null) {
|
||||
return current.value;
|
||||
}
|
||||
|
||||
if (ongoingFetch.value !== null) {
|
||||
return ongoingFetch.value;
|
||||
}
|
||||
|
||||
ongoingFetch.value = fetchSession().finally(() => {
|
||||
ongoingFetch.value = null;
|
||||
});
|
||||
return ongoingFetch.value;
|
||||
}
|
||||
return {
|
||||
// State
|
||||
current,
|
||||
error,
|
||||
loading,
|
||||
user,
|
||||
notification_counts,
|
||||
organization,
|
||||
self,
|
||||
urls,
|
||||
// Actions
|
||||
fetchSession,
|
||||
get,
|
||||
isAuthenticated,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,15 +1,16 @@
|
|||
import { defineStore } from "pinia";
|
||||
import { ref } from "vue";
|
||||
import { User } from "../types";
|
||||
import { SSEManager } from "../SSEManager";
|
||||
import { useSessionStore } from "./session";
|
||||
import { Session, User } from "@/types";
|
||||
import { SSEManager } from "@/SSEManager";
|
||||
import { useSessionStore } from "@/store/session";
|
||||
|
||||
export const useUserStore = defineStore("users", () => {
|
||||
// State
|
||||
const _byID = ref<Map<number, User>>(new Map());
|
||||
const all = ref<User[] | null>(null);
|
||||
const loading = ref(false);
|
||||
const error = ref(null);
|
||||
const loading = ref(false);
|
||||
const ongoingFetch = ref<Promise<User[]> | null>(null);
|
||||
|
||||
// Subscription
|
||||
SSEManager.subscribe("*", (e) => {
|
||||
|
|
@ -24,11 +25,8 @@ export const useUserStore = defineStore("users", () => {
|
|||
return result;
|
||||
}
|
||||
async function fetchAll(): Promise<User[]> {
|
||||
const session = useSessionStore();
|
||||
if (session.urls == null) {
|
||||
throw new Error("can't fetch without user URL data");
|
||||
}
|
||||
|
||||
const sessionStore = useSessionStore();
|
||||
const session = await sessionStore.get();
|
||||
loading.value = true;
|
||||
error.value = null;
|
||||
try {
|
||||
|
|
@ -41,17 +39,31 @@ export const useUserStore = defineStore("users", () => {
|
|||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
const data = await response.json();
|
||||
all.value = data.users;
|
||||
for (const u of data.users) {
|
||||
const users = await response.json();
|
||||
all.value = users;
|
||||
for (const u of users) {
|
||||
_byID.value.set(u.id, u);
|
||||
}
|
||||
return data.users;
|
||||
return users;
|
||||
} catch (err) {
|
||||
console.error("Error loading users:", err);
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
async function withAll(): Promise<User[]> {
|
||||
if (all.value != null) {
|
||||
return all.value;
|
||||
}
|
||||
|
||||
if (ongoingFetch.value !== null) {
|
||||
return ongoingFetch.value;
|
||||
}
|
||||
|
||||
ongoingFetch.value = fetchAll().finally(() => {
|
||||
ongoingFetch.value = null;
|
||||
});
|
||||
return ongoingFetch.value;
|
||||
}
|
||||
async function fetchOne(id: number) {
|
||||
const session = useSessionStore();
|
||||
if (session.urls == null) {
|
||||
|
|
@ -82,5 +94,6 @@ export const useUserStore = defineStore("users", () => {
|
|||
byID,
|
||||
fetchAll,
|
||||
fetchOne,
|
||||
withAll,
|
||||
};
|
||||
});
|
||||
|
|
|
|||
20
ts/types.ts
20
ts/types.ts
|
|
@ -169,6 +169,19 @@ export interface ReviewTaskPool {
|
|||
site: Site;
|
||||
}
|
||||
|
||||
export interface SessionNotificationCounts {
|
||||
communication: number;
|
||||
home: number;
|
||||
review: number;
|
||||
}
|
||||
export interface Session {
|
||||
impersonating?: string;
|
||||
notifications: Notification[];
|
||||
notification_counts: SessionNotificationCounts;
|
||||
organization: Organization;
|
||||
self: User;
|
||||
urls: URLs;
|
||||
}
|
||||
export interface Site {
|
||||
address: Address;
|
||||
created: string;
|
||||
|
|
@ -229,9 +242,6 @@ export interface User {
|
|||
display_name: string;
|
||||
id: number;
|
||||
initials: string;
|
||||
notifications: Notification[];
|
||||
notification_counts: UserNotificationCounts;
|
||||
organization: Organization;
|
||||
role: string;
|
||||
tags: string[];
|
||||
uri: string;
|
||||
|
|
@ -242,10 +252,6 @@ export interface UserNotificationCounts {
|
|||
home: number;
|
||||
review: number;
|
||||
}
|
||||
export interface UserResponse {
|
||||
self: User;
|
||||
urls: URLs;
|
||||
}
|
||||
export interface Water {
|
||||
access_comments: string;
|
||||
access_gate: boolean;
|
||||
|
|
|
|||
|
|
@ -183,8 +183,8 @@ const dashboard = reactive({
|
|||
const session = useSessionStore();
|
||||
onMounted(async () => {});
|
||||
function mapBounds(): Bounds | undefined {
|
||||
if (session.user?.organization.service_area) {
|
||||
return session.user?.organization.service_area;
|
||||
if (session.organization?.service_area) {
|
||||
return session.organization?.service_area;
|
||||
}
|
||||
return undefined;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -146,14 +146,14 @@ tr.has-error {
|
|||
</div>
|
||||
|
||||
<div class="card mb-4">
|
||||
<div v-if="session.user == null">
|
||||
<div v-if="session.organization == null || session.self == null">
|
||||
<p>loading</p>
|
||||
</div>
|
||||
<div v-else>
|
||||
<MapMultipoint
|
||||
:bounds="session.user?.organization.service_area"
|
||||
:bounds="session.organization!.service_area"
|
||||
:markers="[]"
|
||||
:organizationId="session.user?.organization.id"
|
||||
:organizationId="session.organization!.id"
|
||||
:tegola="session.urls?.tegola ?? ''"
|
||||
></MapMultipoint>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ pre {
|
|||
Display Name
|
||||
</label>
|
||||
<input
|
||||
id="displayName"
|
||||
id="display_name"
|
||||
v-model="user.display_name"
|
||||
type="text"
|
||||
class="form-control"
|
||||
|
|
@ -122,7 +122,7 @@ pre {
|
|||
<label for="userRole" class="form-label fw-bold">
|
||||
User Role
|
||||
</label>
|
||||
<select id="userRole" v-model="user.role" class="form-select">
|
||||
<select id="role" v-model="user.role" class="form-select">
|
||||
<option value="">Select a role</option>
|
||||
<option
|
||||
v-for="option in optionRoles"
|
||||
|
|
@ -396,7 +396,8 @@ const cancelChanges = () => {
|
|||
}
|
||||
};
|
||||
onMounted(() => {
|
||||
userStore.fetchAll().then((users) => {
|
||||
userStore.withAll().then((users) => {
|
||||
console.log("got users. looking for match", users, props.id);
|
||||
for (const u of users) {
|
||||
if (u.id == props.id) {
|
||||
user.value = u;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue