Skip to content

Commit

Permalink
feat: users can be granted access to projects
Browse files Browse the repository at this point in the history
Signed-off-by: Henry Gressmann <mail@henrygressmann.de>
  • Loading branch information
explodingcamera committed Dec 16, 2024
1 parent 73d50ad commit f8a56c5
Show file tree
Hide file tree
Showing 9 changed files with 111 additions and 111 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ Since this is not a library, this changelog focuses on the changes that are rele

- Improved query caching to prevent unnecessary database queries
- Added Country Code to Google Referrer URLs
- Improved Multi-User Support (Non-admin users can now be granted access to specific projects)

## v1.0.0 - 2024-12-06

Expand Down
112 changes: 23 additions & 89 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 6 additions & 1 deletion src/app/core/sessions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,12 @@ impl LiwanSessions {
Ok(models::User {
username: row.get("username")?,
role: row.get::<_, String>("role")?.try_into().unwrap_or_default(),
projects: row.get::<_, String>("projects")?.split(',').map(str::to_string).collect(),
projects: row
.get::<_, String>("projects")?
.split(',')
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect(),
})
});

Expand Down
14 changes: 12 additions & 2 deletions src/app/core/users.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,12 @@ impl LiwanUsers {
Ok(models::User {
username: row.get("username")?,
role: row.get::<_, String>("role")?.try_into().unwrap_or_default(),
projects: row.get::<_, String>("projects")?.split(',').map(str::to_string).collect(),
projects: row
.get::<_, String>("projects")?
.split(',')
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect(),
})
});
user.map_err(|_| eyre::eyre!("user not found"))
Expand All @@ -45,7 +50,12 @@ impl LiwanUsers {
Ok(models::User {
username: row.get("username")?,
role: row.get::<_, String>("role")?.try_into().unwrap_or_default(),
projects: row.get::<_, String>("projects")?.split(',').map(str::to_string).collect(),
projects: row
.get::<_, String>("projects")?
.split(',')
.filter(|s| !s.is_empty())
.map(str::to_string)
.collect(),
})
})?;
Ok(users.collect::<Result<Vec<models::User>, rusqlite::Error>>()?)
Expand Down
2 changes: 1 addition & 1 deletion tracker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"homepage": "https://liwan.dev",
"repository": {
"type": "git",
"url": "https://github.com/explodingcamera/liwan",
"url": "git+https://github.com/explodingcamera/liwan.git",
"directory": "tracker"
},
"license": "MIT",
Expand Down
Binary file modified web/bun.lockb
Binary file not shown.
6 changes: 3 additions & 3 deletions web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,9 +15,9 @@
"@icons-pack/react-simple-icons": "^10.2.0",
"@nivo/line": "^0.88.0",
"@picocss/pico": "^2.0.6",
"@radix-ui/react-accordion": "^1.2.1",
"@radix-ui/react-dialog": "^1.1.2",
"@radix-ui/react-tabs": "^1.1.1",
"@radix-ui/react-accordion": "^1.2.2",
"@radix-ui/react-dialog": "^1.1.3",
"@radix-ui/react-tabs": "^1.1.2",
"@tanstack/react-query": "^5.62.7",
"d3-array": "^3.2.4",
"d3-axis": "^3.0.0",
Expand Down
51 changes: 41 additions & 10 deletions web/src/components/settings/dialogs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -523,6 +523,11 @@ const roles = ["admin", "user"] as const;

export const EditUser = ({ user, trigger }: { user: UserResponse; trigger: ReactElement }) => {
const closeRef = useRef<HTMLButtonElement>(null);

const { projects } = useProjects();
const projectTags = useMemo(() => projects.map((p) => ({ value: p.id, label: p.displayName })), [projects]);
const [selectedProjects, setSelectedProjects] = useState<Tag[]>([]);

const { mutate, error, reset } = useMutation({
mutationFn: api["/api/dashboard/user/{username}"].put,
onSuccess: () => {
Expand All @@ -533,12 +538,31 @@ export const EditUser = ({ user, trigger }: { user: UserResponse; trigger: React
onError: console.error,
});

// biome-ignore lint/correctness/useExhaustiveDependencies: don't want to re-run this effect when projects change
useEffect(() => {
setSelectedProjects(
user.projects.map((projectId) => {
const p = projects.find((p) => p.id === projectId);
return {
value: projectId,
label: p ? p.displayName : projectId,
};
}),
);
}, [user.projects]);

const handleSubmit = (e: React.FormEvent) => {
e.preventDefault();
e.stopPropagation();

const form = e.target as HTMLFormElement;
const { role } = Object.fromEntries(new FormData(form)) as { role: (typeof roles)[number] };
mutate({ params: { username: user.username }, json: { role, projects: [] } });
const { admin } = Object.fromEntries(new FormData(form)) as { admin: string };
const role = admin === "on" ? "admin" : "user";

mutate({
params: { username: user.username },
json: { role, projects: selectedProjects.map((tag) => tag.value as string) },
});
};

return (
Expand All @@ -550,16 +574,23 @@ export const EditUser = ({ user, trigger }: { user: UserResponse; trigger: React
trigger={trigger}
>
<form onSubmit={handleSubmit}>
<Tags
labelText="Projects"
selected={selectedProjects}
suggestions={projectTags}
onAdd={(tag) => setSelectedProjects((rest) => [...rest, tag])}
onDelete={(i) => setSelectedProjects(selectedProjects.filter((_, index) => index !== i))}
noOptionsText="No matching projects"
/>
<label>
Role
<select name="role" defaultValue={user.role}>
{roles.map((r) => (
<option key={r} value={r}>
{r}
</option>
))}
</select>
{/* biome-ignore lint/a11y/useAriaPropsForRole: this is an uncontrolled component */}
<input name="admin" type="checkbox" role="switch" defaultChecked={user.role === "admin"} />
Enable Administrator Access
<br />
<small>Administators can edit and create projects, entities, and users.</small>
</label>
<br />

<div className="grid">
<Dialog.Close asChild>
<button className="secondary outline" type="button" ref={closeRef}>
Expand Down
Loading

0 comments on commit f8a56c5

Please sign in to comment.