Skip to content

Commit

Permalink
logo in header, display creation time and user avatar in cards, move …
Browse files Browse the repository at this point in the history
…download button
  • Loading branch information
webdevcody committed Feb 24, 2024
1 parent e7a386e commit 978abec
Show file tree
Hide file tree
Showing 16 changed files with 334 additions and 39 deletions.
13 changes: 9 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,12 @@ Check out our [Next.js deployment documentation](https://nextjs.org/docs/deploym

## TODO

- filtering
- favorites
- trash
- sharing
- uploaded by
- upload date
- filters by type
- shared with me
- table view
- view toggle
- folders
- icon
- landing page
2 changes: 2 additions & 0 deletions convex/_generated/api.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type {
FunctionReference,
} from "convex/server";
import type * as clerk from "../clerk.js";
import type * as crons from "../crons.js";
import type * as files from "../files.js";
import type * as http from "../http.js";
import type * as users from "../users.js";
Expand All @@ -29,6 +30,7 @@ import type * as users from "../users.js";
*/
declare const fullApi: ApiFromModules<{
clerk: typeof clerk;
crons: typeof crons;
files: typeof files;
http: typeof http;
users: typeof users;
Expand Down
12 changes: 12 additions & 0 deletions convex/crons.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
import { cronJobs } from "convex/server";
import { internal } from "./_generated/api";

const crons = cronJobs();

crons.interval(
"delete any old files marked for deletion",
{ minutes: 1 },
internal.files.deleteAllFiles
);

export default crons;
60 changes: 58 additions & 2 deletions convex/files.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
import { ConvexError, v } from "convex/values";
import { MutationCtx, QueryCtx, mutation, query } from "./_generated/server";
import {
MutationCtx,
QueryCtx,
internalMutation,
mutation,
query,
} from "./_generated/server";
import { getUser } from "./users";
import { fileTypes } from "./schema";
import { Id } from "./_generated/dataModel";
Expand Down Expand Up @@ -62,6 +68,7 @@ export const createFile = mutation({
orgId: args.orgId,
fileId: args.fileId,
type: args.type,
userId: hasAccess.user._id,
});
},
});
Expand All @@ -71,6 +78,7 @@ export const getFiles = query({
orgId: v.string(),
query: v.optional(v.string()),
favorites: v.optional(v.boolean()),
deletedOnly: v.optional(v.boolean()),
},
async handler(ctx, args) {
const hasAccess = await hasAccessToOrg(ctx, args.orgId);
Expand Down Expand Up @@ -105,10 +113,33 @@ export const getFiles = query({
);
}

if (args.deletedOnly) {
files = files.filter((file) => file.shouldDelete);
} else {
files = files.filter((file) => !file.shouldDelete);
}

return files;
},
});

export const deleteAllFiles = internalMutation({
args: {},
async handler(ctx) {
const files = await ctx.db
.query("files")
.withIndex("by_shouldDelete", (q) => q.eq("shouldDelete", true))
.collect();

await Promise.all(
files.map(async (file) => {
await ctx.storage.delete(file.fileId);
return await ctx.db.delete(file._id);
})
);
},
});

export const deleteFile = mutation({
args: { fileId: v.id("files") },
async handler(ctx, args) {
Expand All @@ -126,7 +157,32 @@ export const deleteFile = mutation({
throw new ConvexError("you have no admin access to delete");
}

await ctx.db.delete(args.fileId);
await ctx.db.patch(args.fileId, {
shouldDelete: true,
});
},
});

export const restoreFile = mutation({
args: { fileId: v.id("files") },
async handler(ctx, args) {
const access = await hasAccessToFile(ctx, args.fileId);

if (!access) {
throw new ConvexError("no access to file");
}

const isAdmin =
access.user.orgIds.find((org) => org.orgId === access.file.orgId)
?.role === "admin";

if (!isAdmin) {
throw new ConvexError("you have no admin access to delete");
}

await ctx.db.patch(args.fileId, {
shouldDelete: false,
});
},
});

Expand Down
21 changes: 17 additions & 4 deletions convex/http.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,20 +25,33 @@ http.route({
switch (result.type) {
case "user.created":
await ctx.runMutation(internal.users.createUser, {
tokenIdentifier: `https://poetic-salmon-66.clerk.accounts.dev|${result.data.id}`,
tokenIdentifier: `https://${process.env.CLERK_HOSTNAME}|${result.data.id}`,
name: `${result.data.first_name ?? ""} ${
result.data.last_name ?? ""
}`,
image: result.data.image_url,
});
break;
case "user.updated":
await ctx.runMutation(internal.users.updateUser, {
tokenIdentifier: `https://${process.env.CLERK_HOSTNAME}|${result.data.id}`,
name: `${result.data.first_name ?? ""} ${
result.data.last_name ?? ""
}`,
image: result.data.image_url,
});
break;
case "organizationMembership.created":
await ctx.runMutation(internal.users.addOrgIdToUser, {
tokenIdentifier: `https://poetic-salmon-66.clerk.accounts.dev|${result.data.public_user_data.user_id}`,
tokenIdentifier: `https://${process.env.CLERK_HOSTNAME}|${result.data.public_user_data.user_id}`,
orgId: result.data.organization.id,
role: result.data.role === "admin" ? "admin" : "member",
role: result.data.role === "org:admin" ? "admin" : "member",
});
break;
case "organizationMembership.updated":
console.log(result.data.role);
await ctx.runMutation(internal.users.updateRoleInOrgForUser, {
tokenIdentifier: `https://poetic-salmon-66.clerk.accounts.dev|${result.data.public_user_data.user_id}`,
tokenIdentifier: `https://${process.env.CLERK_HOSTNAME}|${result.data.public_user_data.user_id}`,
orgId: result.data.organization.id,
role: result.data.role === "org:admin" ? "admin" : "member",
});
Expand Down
8 changes: 7 additions & 1 deletion convex/schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,20 @@ export default defineSchema({
type: fileTypes,
orgId: v.string(),
fileId: v.id("_storage"),
}).index("by_orgId", ["orgId"]),
userId: v.id("users"),
shouldDelete: v.optional(v.boolean()),
})
.index("by_orgId", ["orgId"])
.index("by_shouldDelete", ["shouldDelete"]),
favorites: defineTable({
fileId: v.id("files"),
orgId: v.string(),
userId: v.id("users"),
}).index("by_userId_orgId_fileId", ["userId", "orgId", "fileId"]),
users: defineTable({
tokenIdentifier: v.string(),
name: v.optional(v.string()),
image: v.optional(v.string()),
orgIds: v.array(
v.object({
orgId: v.string(),
Expand Down
44 changes: 42 additions & 2 deletions convex/users.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import { ConvexError, v } from "convex/values";
import { MutationCtx, QueryCtx, internalMutation } from "./_generated/server";
import {
MutationCtx,
QueryCtx,
internalMutation,
query,
} from "./_generated/server";
import { roles } from "./schema";

export async function getUser(
Expand All @@ -21,11 +26,34 @@ export async function getUser(
}

export const createUser = internalMutation({
args: { tokenIdentifier: v.string() },
args: { tokenIdentifier: v.string(), name: v.string(), image: v.string() },
async handler(ctx, args) {
await ctx.db.insert("users", {
tokenIdentifier: args.tokenIdentifier,
orgIds: [],
name: args.name,
image: args.image,
});
},
});

export const updateUser = internalMutation({
args: { tokenIdentifier: v.string(), name: v.string(), image: v.string() },
async handler(ctx, args) {
const user = await ctx.db
.query("users")
.withIndex("by_tokenIdentifier", (q) =>
q.eq("tokenIdentifier", args.tokenIdentifier)
)
.first();

if (!user) {
throw new ConvexError("no user with this token found");
}

await ctx.db.patch(user._id, {
name: args.name,
image: args.image,
});
},
});
Expand Down Expand Up @@ -61,3 +89,15 @@ export const updateRoleInOrgForUser = internalMutation({
});
},
});

export const getUserProfile = query({
args: { userId: v.id("users") },
async handler(ctx, args) {
const user = await ctx.db.get(args.userId);

return {
name: user?.name,
image: user?.image,
};
},
});
37 changes: 37 additions & 0 deletions package-lock.json

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

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
"@clerk/nextjs": "^4.29.7",
"@hookform/resolvers": "^3.3.4",
"@radix-ui/react-alert-dialog": "^1.0.5",
"@radix-ui/react-avatar": "^1.0.4",
"@radix-ui/react-dialog": "^1.0.5",
"@radix-ui/react-dropdown-menu": "^2.0.6",
"@radix-ui/react-label": "^2.0.2",
Expand All @@ -21,6 +22,7 @@
"class-variance-authority": "^0.7.0",
"clsx": "^2.1.0",
"convex": "^1.9.1",
"date-fns": "^3.3.1",
"lucide-react": "^0.336.0",
"next": "14.1.0",
"react": "^18",
Expand Down
Binary file added public/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 3 additions & 1 deletion src/app/dashboard/_components/file-browser.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,11 @@ function Placeholder() {
export function FileBrowser({
title,
favoritesOnly,
deletedOnly,
}: {
title: string;
favoritesOnly?: boolean;
deletedOnly?: boolean;
}) {
const organization = useOrganization();
const user = useUser();
Expand All @@ -50,7 +52,7 @@ export function FileBrowser({

const files = useQuery(
api.files.getFiles,
orgId ? { orgId, query, favorites: favoritesOnly } : "skip"
orgId ? { orgId, query, favorites: favoritesOnly, deletedOnly } : "skip"
);
const isLoading = files === undefined;

Expand Down
Loading

0 comments on commit 978abec

Please sign in to comment.