Skip to content

feat: add star #416

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Aug 2, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions api/prisma/migrations/20230802033453_add_star/migration.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
-- CreateTable
CREATE TABLE "_STAR" (
"A" TEXT NOT NULL,
"B" TEXT NOT NULL
);

-- CreateIndex
CREATE UNIQUE INDEX "_STAR_AB_unique" ON "_STAR"("A", "B");

-- CreateIndex
CREATE INDEX "_STAR_B_index" ON "_STAR"("B");

-- AddForeignKey
ALTER TABLE "_STAR" ADD CONSTRAINT "_STAR_A_fkey" FOREIGN KEY ("A") REFERENCES "Repo"("id") ON DELETE CASCADE ON UPDATE CASCADE;

-- AddForeignKey
ALTER TABLE "_STAR" ADD CONSTRAINT "_STAR_B_fkey" FOREIGN KEY ("B") REFERENCES "User"("id") ON DELETE CASCADE ON UPDATE CASCADE;
2 changes: 2 additions & 0 deletions api/prisma/schema.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ model User {
profile Profile?
Repo Repo[] @relation("OWNER")
sharedRepos Repo[] @relation("COLLABORATOR")
stars Repo[] @relation("STAR")
UserRepoData UserRepoData[]

codeiumAPIKey String? @default("")
Expand Down Expand Up @@ -81,6 +82,7 @@ model Repo {
// Edit pod content likely won't update this updatedAt field.
updatedAt DateTime @default(now()) @updatedAt
UserRepoData UserRepoData[]
stargazers User[] @relation("STAR")
}

enum PodType {
Expand Down
55 changes: 54 additions & 1 deletion api/src/resolver_repo.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
// nanoid v4 does not work with nodejs. https://github.com/ai/nanoid/issues/365
import { customAlphabet } from "nanoid/async";
import { lowercase, numbers } from "nanoid-dictionary";
import prisma from './client'
import prisma from "./client";

const nanoid = customAlphabet(lowercase + numbers, 20);

Expand Down Expand Up @@ -55,6 +55,7 @@ async function myRepos(_, __, { userId }) {
userId: userId,
},
},
stargazers: true,
},
});
// Sort by last access time.
Expand Down Expand Up @@ -90,6 +91,9 @@ async function myCollabRepos(_, __, { userId }) {
some: { id: userId },
},
},
include: {
stargazers: true,
},
});
return repos;
}
Expand Down Expand Up @@ -383,6 +387,53 @@ async function deleteCollaborator(_, { repoId, collaboratorId }, { userId }) {
return true;
}

async function star(_, { repoId }, { userId }) {
// make sure the repo is visible by this user
if (!userId) throw new Error("Not authenticated.");
let repo = await prisma.repo.findFirst({
where: {
id: repoId,
OR: [
{ owner: { id: userId || "undefined" } },
{ collaborators: { some: { id: userId || "undefined" } } },
{ public: true },
],
},
});
if (!repo) throw new Error("Repo not found.");
// 3. add the user to the repo
await prisma.repo.update({
where: {
id: repoId,
},
data: {
stargazers: { connect: { id: userId } },
},
});
return true;
}

async function unstar(_, { repoId }, { userId }) {
if (!userId) throw new Error("Not authenticated.");
// 1. find the repo
const repo = await prisma.repo.findFirst({
where: {
id: repoId,
},
});
// 2. delete the user from the repo
if (!repo) throw new Error("Repo not found.");
await prisma.repo.update({
where: {
id: repoId,
},
data: {
stargazers: { disconnect: { id: userId } },
},
});
return true;
}

async function updatePod(_, { id, repoId, input }, { userId }) {
if (!userId) throw new Error("Not authenticated.");
await ensureRepoEditAccess({ repoId, userId });
Expand Down Expand Up @@ -576,5 +627,7 @@ export default {
addCollaborator,
updateVisibility,
deleteCollaborator,
star,
unstar,
},
};
3 changes: 3 additions & 0 deletions api/src/typedefs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export const typeDefs = gql`
pods: [Pod]
edges: [Edge]
userId: ID!
stargazers: [User]
collaborators: [User]
public: Boolean
createdAt: String
Expand Down Expand Up @@ -141,6 +142,8 @@ export const typeDefs = gql`
updateVisibility(repoId: String!, isPublic: Boolean!): Boolean
addCollaborator(repoId: String!, email: String!): Boolean
deleteCollaborator(repoId: String!, collaboratorId: String!): Boolean
star(repoId: ID!): Boolean
unstar(repoId: ID!): Boolean

exportJSON(repoId: String!): String!
exportFile(repoId: String!): String!
Expand Down
119 changes: 90 additions & 29 deletions ui/src/pages/repos.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ import StopCircleIcon from "@mui/icons-material/StopCircle";
import CircularProgress from "@mui/material/CircularProgress";
import SourceIcon from "@mui/icons-material/Source";
import DescriptionOutlinedIcon from "@mui/icons-material/DescriptionOutlined";
import StarIcon from "@mui/icons-material/Star";
import StarBorderIcon from "@mui/icons-material/StarBorder";
import Tooltip from "@mui/material/Tooltip";
import IconButton from "@mui/material/IconButton";
import ShareIcon from "@mui/icons-material/Share";
Expand All @@ -32,6 +34,7 @@ import {
DialogActions,
DialogContent,
DialogTitle,
Stack,
useTheme,
} from "@mui/material";
import { useAuth } from "../lib/auth";
Expand All @@ -45,6 +48,9 @@ const GET_REPOS = gql`
name
id
public
stargazers {
id
}
updatedAt
createdAt
}
Expand Down Expand Up @@ -74,39 +80,91 @@ function RepoLine({

// haochen: any reason not using Loading state from useMutation?
const [killing, setKilling] = useState(false);

const [star, { loading: starLoading }] = useMutation(
gql`
mutation star($repoId: ID!) {
star(repoId: $repoId)
}
`,
{
refetchQueries: ["GetRepos"],
}
);
const [unstar, { loading: unstarLoading }] = useMutation(
gql`
mutation unstar($repoId: ID!) {
unstar(repoId: $repoId)
}
`,
{
refetchQueries: ["GetRepos"],
}
);

return (
<TableRow
key={repo.id}
sx={{ "&:last-child td, &:last-child th": { border: 0 } }}
>
<TableCell align="center">
<Link
component={ReactLink}
to={`/repo/${repo.id}`}
sx={
deleting
? {
color: theme.palette.action.disabled,
textDecorationColor: theme.palette.action.disabled,
pointerEvents: "none",
}
: {}
}
>
<Box
sx={{
display: "flex",
alignItems: "center",
}}
<TableCell align="left">
<Stack direction="row">
{repo.stargazers?.map((_) => _.id).includes(me.id) ? (
<Tooltip title="unstar">
<IconButton
size="small"
onClick={() => {
unstar({ variables: { repoId: repo.id } });
}}
disabled={unstarLoading}
>
<StarIcon
fontSize="inherit"
sx={{
color: "orange",
}}
/>
{repo.stargazers.length}
</IconButton>
</Tooltip>
) : (
<Tooltip title="star">
<IconButton
size="small"
onClick={() => {
star({ variables: { repoId: repo.id } });
}}
disabled={starLoading}
>
<StarBorderIcon fontSize="inherit" />
{repo.stargazers.length}
</IconButton>
</Tooltip>
)}

<Link
component={ReactLink}
to={`/repo/${repo.id}`}
sx={
deleting
? {
color: theme.palette.action.disabled,
textDecorationColor: theme.palette.action.disabled,
pointerEvents: "none",
}
: {}
}
>
<DescriptionOutlinedIcon
sx={{
marginRight: "5px",
}}
/>
{repo.name || "Untitled"}
</Box>
</Link>
<Stack direction="row">
<DescriptionOutlinedIcon
sx={{
marginRight: "5px",
}}
/>
{repo.name || "Untitled"}
</Stack>
</Link>
</Stack>
</TableCell>
<TableCell align="left">
<Chip
Expand Down Expand Up @@ -388,6 +446,9 @@ function SharedWithMe() {
name
id
public
stargazers {
id
}
updatedAt
createdAt
}
Expand Down Expand Up @@ -522,7 +583,7 @@ export default function Page() {
return <Box>Loading user ..</Box>;
}
return (
<Box sx={{ maxWidth: "sm", alignItems: "center", m: "auto" }}>
<Box sx={{ maxWidth: "md", alignItems: "center", m: "auto" }}>
{/* TODO some meta information about the user */}
{/* <CurrentUser /> */}
{/* TODO the repos of this user */}
Expand All @@ -538,7 +599,7 @@ export default function Page() {
started.
</Box>
{!isSignedIn() && (
<Box sx={{ maxWidth: "sm", alignItems: "center", m: "auto" }}>
<Box sx={{ alignItems: "center", m: "auto" }}>
<Alert severity="warning">
Please note that you are a{" "}
<Box component="span" color="red">
Expand Down