Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
86cf571
fix: profile card
jiasunzhu613 Jul 31, 2025
0a319a9
feat: add rate limit by user
jiasunzhu613 Aug 1, 2025
7951c93
Add shadcn stepper and custom stepper
mh-anwar Aug 2, 2025
339187d
Complete migration to CodeBlock
mh-anwar Aug 2, 2025
e82994a
Install stepper and separator
mh-anwar Aug 2, 2025
ad15883
Migrate custom stepper to component
mh-anwar Aug 2, 2025
d9152a9
Migrate UI to match DB
mh-anwar Aug 2, 2025
0fb9866
Move to native/npm components
mh-anwar Aug 3, 2025
3ab42a2
Add metadata, remove unused code
mh-anwar Aug 3, 2025
9f7e081
Add metadata related files
mh-anwar Aug 3, 2025
3d4ac2a
Heavily refactor to use locally generated avatars
mh-anwar Aug 3, 2025
1c2acee
Add dropdown to navbar
mh-anwar Aug 3, 2025
8fef870
Remove unused files
mh-anwar Aug 3, 2025
3a486ad
Fix to match new API's, add error handling
mh-anwar Aug 3, 2025
efbace4
Add not found page
mh-anwar Aug 3, 2025
c09c3c0
fix: remove scene draggability
mh-anwar Aug 3, 2025
c9c20fb
Improve logic, refactor for consistency, error handling
mh-anwar Aug 3, 2025
bb990a0
Add experimental u/ route
mh-anwar Aug 3, 2025
6c52fae
fix: keep stepper in sync with route
mh-anwar Aug 3, 2025
cbe9ca3
feat: subdomain field in db
jiasunzhu613 Aug 4, 2025
395787d
Create stricter .prettierrc
mh-anwar Aug 4, 2025
02f5409
Move dialog to appropriate location
mh-anwar Aug 4, 2025
6275f87
Move popover to be in vicinity of shadcn components
mh-anwar Aug 4, 2025
2895195
Remove unused imports, code, completed TODO's
mh-anwar Aug 4, 2025
0c52e60
fix: change join section so that it supports manual approval
jiasunzhu613 Aug 4, 2025
68082f6
Create config to prevent constant auto deploys
mh-anwar Aug 4, 2025
a28f960
Fix null error
mh-anwar Aug 4, 2025
39c2113
Run prettier
mh-anwar Aug 4, 2025
22b3fac
fix: mock data
jiasunzhu613 Aug 4, 2025
101668b
Remove unused component
mh-anwar Aug 4, 2025
3df2423
Add basic sitemap
mh-anwar Aug 4, 2025
7b20efb
Merge pull request #72 from uoft-webring/anwar/refactor
jiasunzhu613 Aug 4, 2025
365dcc6
Test path changes as fix
mh-anwar Aug 4, 2025
d4652bd
Merge branch 'main' into anwar/test-fixes
mh-anwar Aug 4, 2025
cf2aaf3
Merge pull request #73 from uoft-webring/anwar/test-fixes
jiasunzhu613 Aug 4, 2025
a35d9b6
feat: admin tasks
jiasunzhu613 Aug 4, 2025
40b1de0
Merge pull request #74 from uoft-webring/jzhu/contrib2
jiasunzhu613 Aug 4, 2025
82478ec
Update and rename CONTRIBUTING.MD to CONTRIBUTING.md
jiasunzhu613 Aug 4, 2025
c082723
Merge pull request #76 from uoft-webring/jiasunzhu613-patch-1
jiasunzhu613 Aug 4, 2025
a206724
Create SECURITY.md
mh-anwar Aug 4, 2025
e10837e
logo white
jiasunzhu613 Aug 6, 2025
6fad5c1
black logo
jiasunzhu613 Aug 6, 2025
196b450
logo
jiasunzhu613 Aug 6, 2025
b1fe9c5
feat: dont allow images to be draggable
jiasunzhu613 Aug 6, 2025
30667b0
feat: enum type
jiasunzhu613 Aug 6, 2025
879a4a3
Merge pull request #78 from uoft-webring/logo
jiasunzhu613 Aug 6, 2025
388fbbb
Merge pull request #77 from uoft-webring/anwar/security
jiasunzhu613 Aug 6, 2025
2fc756f
fix: profile card text wrapping
jiasunzhu613 Aug 6, 2025
f50e746
fix: env
jiasunzhu613 Aug 7, 2025
294a82b
feat: tabs for join code
jiasunzhu613 Aug 7, 2025
18692c6
Update page.tsx
jiasunzhu613 Aug 7, 2025
7d7e350
Merge pull request #79 from uoft-webring/jzhu/join-section-code
jiasunzhu613 Aug 7, 2025
81358ff
feat: add rate limit by user
jiasunzhu613 Aug 1, 2025
abcd69b
fix: filter webring data
jiasunzhu613 Aug 7, 2025
c5ec13c
Merge branch 'jzhu/fixes' of github.com:uoft-webring/webring into jzh…
jiasunzhu613 Aug 7, 2025
e919d65
dropdown
jiasunzhu613 Aug 7, 2025
f70986f
drop
jiasunzhu613 Aug 7, 2025
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
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
@mh-anwar @jiasunzhu613 @TheAmanM @mohamad-damaj
8 changes: 7 additions & 1 deletion .prettierrc
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,11 @@
"trailingComma": "es5",
"tabWidth": 4,
"semi": true,
"singleQuote": false
"singleQuote": false,
"bracketSpacing": true,
"arrowParens": "always",
"endOfLine": "lf",
"printWidth": 110,
"proseWrap": "preserve",
"htmlWhitespaceSensitivity": "css"
}
51 changes: 51 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
# Welcome to UofT Webring's Contributing Guidelines and Developer Quickstart!

We welcome contribution through pull requests, questions, feature requests as well as bug reports (issues)!

To push changes from a branch, you should first create a fork. This will create your copy of the repo and ensure that you are able to push changes to Github and make PRs.

## Committing Changes and Writing Pull Requests

**Committing changes**

When committing changes please use [semantic commit messages](https://gist.github.com/joshbuchea/6f47e86d2510bce28f8e7f42ae84c716) and describe what the commit changed briefly using.

**Pull Requests**

When making pull requests please provide a descriptive title explaning the overall scope of the PR and provide details in a "description" section.

We do not enforce a PR template to follow but if you are having trouble writing one, here is one you can follow:

```
Closes #{insert ticket number}

## Description
{
insert more detailed description here,
explaining what was changed
}

### Missing features
{
Add all missing features from related tickets here, if any,
otherwise delete this section
}
```

## Getting Started Locally

First, run the development frontend server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Then start the DB locally using the [Supabase CLI](https://supabase.com/docs/guides/local-development/cli/getting-started).

**First time using Supabase CLI?** Refer to our [quickstart](supabase/README.md) to get up to speed then refer to Supabase documentation!
339 changes: 339 additions & 0 deletions LICENSE

Large diffs are not rendered by default.

60 changes: 18 additions & 42 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,18 @@
# Webring

## Local Development Environment Variables (from Supabase CLI)

```
# Local development keys
NEXT_PUBLIC_SUPABASE_URL=http://127.0.0.1:54321
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
NEXT_PUBLIC_SUPABASE_SERVICE_ROLE_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU
HOME_DOMAIN =https://ourdomain/
### Information from running supabase start
# API URL: http://127.0.0.1:54321
# GraphQL URL: http://127.0.0.1:54321/graphql/v1
# S3 Storage URL: http://127.0.0.1:54321/storage/v1/s3
# DB URL: postgresql://postgres:postgres@127.0.0.1:54322/postgres
# Studio URL: http://127.0.0.1:54323
# Inbucket URL: http://127.0.0.1:54324
# JWT secret: super-secret-jwt-token-with-at-least-32-characters-long
# anon key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
# service_role key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU
# S3 Access Key: 625729a08b95bf1b7ff351a663f3a23c
# S3 Secret Key: 850181e4652dd023b7a98c58ae0d2d34bd487ee0cc3254aed6eda37307425907
# S3 Region: local
```

You will also require a `DOMAIN_VALIDATION_SECRET_KEY`.

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
# or
bun dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
# UofT Webring 🪐

A Webring environment providing a unified space for UofT students to gather and showcase their web portfolios.

**Are you a UofT Student? Come [join](https://uoftwebring.com/signup) the UofT Webring community!**

## Technologies

- Frontend: Next.JS
- DB / pseudo-backend: Supabase, Supabase SDK

## Developer Quickstart

Refer to our [contributing guide](CONTRIBUTING.md)!

## Acknowledgements

Special shout out to all the UWaterloo webrings ([CS](https://cs.uwatering.com/), [SE](https://se-webring.xyz/)) for the inspiration! Go check them out and meet all the cool people from there!
20 changes: 20 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
# Security Policy

## Reporting a Vulnerability

If you discover a potential security vulnerability in this project, we appreciate your help in disclosing it responsibly.

### How to Report

Please report security issues by privately filing it, see [here](https://docs.github.com/en/code-security/security-advisories/guidance-on-reporting-and-writing-information-about-vulnerabilities/privately-reporting-a-security-vulnerability#privately-reporting-a-security-vulnerability).
When reporting, please include as much relevant detail as possible:

- Steps to reproduce the vulnerability
- A clear description of the issue and its impact
- Any relevant logs, screenshots, or proof-of-concept code

### Guidelines

- If you're unsure whether the issue qualifies as a vulnerability, it's still better to report it privately first.
- Avoid sharing sensitive data in public issues.
- Use discretion and keep the issue confidential until it is addressed.
16 changes: 16 additions & 0 deletions app/(metadata)/manifest.webmanifest
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"name": "UofT Webring",
"start_url": "/",
"theme_color": "#010414",
"background_color": "#010414",
"display": "fullscreen",
"lang": "en",
"orientation": "portrait",
"icons": [
{
"url": "/favicon.ico",
"sizes": "16x16",
"type": "image/png"
}
]
}
File renamed without changes
13 changes: 13 additions & 0 deletions app/(metadata)/sitemap.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
/* We should make dynamic routes */
import type { MetadataRoute } from "next";

export default function sitemap(): MetadataRoute.Sitemap {
return [
{
url: "https://uoftwebring.com",
lastModified: new Date(),
changeFrequency: "weekly",
priority: 1,
},
];
}
Binary file added app/(metadata)/twitter-image.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
118 changes: 74 additions & 44 deletions app/actions.ts
Original file line number Diff line number Diff line change
@@ -1,67 +1,97 @@
"use server";

import {
createAdminClient,
createClient,
createServiceClient,
} from "@/utils/supabase/server";
import { SafeUser, SafeUserType } from "@/utils/zod";
import { createAdminClient, createClient } from "@/utils/supabase/server";
import { SafeUserType } from "@/utils/zod";
import { PostgrestError } from "@supabase/supabase-js";
import { User } from "@supabase/supabase-js";
import { UserType } from "@/utils/zod";
import { getAuthUser } from "./dashboard/actions";
import { redirect } from "next/navigation";

type FetchRingProfilesResponse =
| {
ringProfiles: SafeUserType[];
error: null;
}
| {
ringProfiles: null;
error: string;
};
const PROFILE_COLUMNS =
"ring_id, tagline, domain, name, validated_user_component, github_url, image_url, is_verified, tags";

export async function fetchRingProfiles(): Promise<FetchRingProfilesResponse> {
console.log("FETCHING PROFILES FOR RING");
// First, we get the client and fetch everything
const supabase = createAdminClient(); // Requires admin client to bypass RLS
// Generic response type for uniform handling
export type ApiResponse<T> = { data: T; error: null } | { data: null; error: string };

// Specific aliases for clarity
type GetAllUserProfilesResponse = ApiResponse<SafeUserType[]>;
type GetUserProfileResponse = ApiResponse<SafeUserType>;
type GetAuthUserProfileResponse = ApiResponse<UserType>;

/**
* Fetches all user profiles from the `profile` table.
* Intended for server use only, as it bypasses Row Level Security (RLS) using an admin client.
*
* @returns {Promise<GetAllUserProfilesResponse>}
* An object containing all user profiles in `data` if successful,
* or an error message in `error` if the operation fails.
*/
export async function getAllUserProfiles(): Promise<GetAllUserProfilesResponse> {
// To fetch all user profiles, we need an admin client to bypass RLS
const supabase = createAdminClient();
const { data, error } = await supabase
.from("profile")
.select(
"ring_id, tagline, domain, name, valid, github_url, image_url, is_verified, tags"
)
.select(PROFILE_COLUMNS)
.order("ring_id", { ascending: true });

if (error) {
return {
ringProfiles: null,
data: null,
error: `Error: ${(error as PostgrestError).message}`,
};
}
if (!data) {
return { ringProfiles: null, error: "Error: No data returned." };
}

// Next, we filter profiles that are not allowed to be in the ring
// Currently, no filtering is being done since a valid domain is required upon registration
// Below is some boiletplate code.

return {
ringProfiles: data,
error: null,
};
return data ? { data, error: null } : { data: null, error: "Error: No data returned." };
}

export const getUserProfile = async () => {
/**
* Retrieves the authenticated user's profile from the `profile` table.
*
* Requires the user to be signed in. The user's ID is obtained from the Supabase auth session.
*
* @returns {Promise<GetAuthUserProfileResponse>}
* An object containing the user's profile in `data` if successful,
* or an error message in `error` if the user is not authenticated or the query fails.
*/
export const getAuthUserProfile = async (): Promise<GetAuthUserProfileResponse> => {
const supabase = await createClient();
const {
data: { user },
error: authError,
} = await supabase.auth.getUser();

const { data, error: dataError } = await supabase
if (!user) {
return { data: null, error: authError?.message ?? "Auth error" };
}

const { data, error } = await supabase.from("profile").select("*").eq("id", user.id).single();

return data
? { data: data as UserType, error: null }
: { data: null, error: error?.message ?? "Error fetching profile" };
};

/**
* Fetches a single user profile by its associated `subdomain` slug.
* Intended for server use, as it bypasses Row Level Security (RLS) using an admin client.
*
* @param {string} slug - The `subdomain` identifier corresponding to the user profile.
* @returns {Promise<GetUserProfileResponse>}
* An object containing the matching user profile in `data` if found,
* or an error message in `error` if the profile is not found or the query fails.
*/
export const getUserProfile = async (slug: string): Promise<GetUserProfileResponse> => {
// To fetch an arbitrary user (not the curr user), we need an admin client to bypass RLS
const supabase = createAdminClient();
const { data, error } = await supabase
.from("profile")
.select("*");
.select(PROFILE_COLUMNS)
.eq("subdomain", slug)
.single();

return {
data: data?.at(0) as UserType,
error: dataError,
};
if (error) {
return {
data: null,
error: `Error: ${(error as PostgrestError).message}`,
};
}
return data ? { data, error: null } : { data: null, error: "Error: No data returned." };
};
15 changes: 3 additions & 12 deletions app/api/revalidate/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,18 +10,12 @@ export async function POST(request: NextRequest) {
// Check if the secret is missing in your environment variables
if (!secret) {
console.error("REVALIDATION_SECRET_TOKEN is not set.");
return NextResponse.json(
{ message: "Server configuration error." },
{ status: 500 }
);
return NextResponse.json({ message: "Server configuration error." }, { status: 500 });
}

// Check if the token from the header matches
if (authHeader !== `Bearer ${secret}`) {
return NextResponse.json(
{ message: "Unauthorized: Invalid token." },
{ status: 401 }
);
return NextResponse.json({ message: "Unauthorized: Invalid token." }, { status: 401 });
}

// 2. Perform the revalidation
Expand All @@ -39,9 +33,6 @@ export async function POST(request: NextRequest) {
});
} catch (err) {
console.error("Error during revalidation:", err);
return NextResponse.json(
{ message: "Error revalidating" },
{ status: 500 }
);
return NextResponse.json({ message: "Error revalidating" }, { status: 500 });
}
}
Loading