Giter VIP home page Giter VIP logo

challenge-foro-alura's Introduction

Challenge ONE | Back End | Foro Alura

Esta es mi solución al último reto de la formación de Backend con Java y Spring del programa ONE. El proyecto consiste en una réplica del backend del foro de la plataforma Alura Latam, en la que todos los estudiantes de la plataforma podemos hacer preguntasy responder preguntas, colaborando e interactuando con otros estudiantes, así como también con profesores y moderadores.

Requerimientos

Los requerimientos para la API REST son los siguientes:

  • ✅ Crear una nueva publicación
  • ✅ Mostrar todas las publicaciones creadas
  • ✅ Mostrar una publicación específica
  • ✅ Actualizar una publicación
  • ✅ Eliminar una publicación

Mi proyecto cumple con todos ellos y, además cumple con los siguientes requerimientos adicionales:

  • ✅ Registro, ingreso y salida de usuarios

  • ✅ Contraseñas encriptadas con BCrypt Password Encoder

  • ✅ Cambio de contraseña y asignar rol a usuario

  • ✅ Autenticación usando JSON Web Tokens (JWT)

  • ✅ Autorización basada en roles y a nivel de métodos

  • ✅ Refresh Tokens

  • ✅ Entidades adicionales: Rol, Refresh Tokens y Etiquetas (Categorías y Subcategorías)

  • ✅ Operaciones CRUD en todas las entidades

  • ✅ Escoger respuesta como solución

  • ✅ Manejo excepciones con mensajes personalizados

  • Tecnologías utilizadas:

EndPoints

Autenticación

Algunos endpoints u operaciones en endpoints requieren autenticación del tipo bearer token. Para recibir dicho token, debes registrar tu usuario e iniciar sesión. Tras lo último, recibirás dos tokens como respuesta: un access token y un refresh token.

  • Access token: es un JWT de corta duración que debes enviar en el header de las peticiones en las que necesitas para autenticarte, así:
Authorization: Bearer jwt.token.aquí
  • Refresh token: este token tiene una mayor duración, pero no es un JWT, por lo que no es un reemplazo del access token. Este token te sirve para generar nuevos access tokens sin necesidad de tener que iniciar sesión cada vez que tu access token expire.
Endpoint Método Acceso Descripción
/api/v1/auth/signup POST Público Crea un usuario en la base de datos
/api/v1/auth/login POST Público Genera par de tokens (access-refresh)
/api/v1/auth/logout POST Privado/Protegido Invalida el refresh token y elimina al usuario del SecurityContextHolder
/api/v1/auth/token/refresh PUT Público Genera un nuevo access token
/api/v1/usuarios/{usuarioId}/roles/{rolId} PUT Privado/Admin Asigna rol a usuario
/api/v1/usuarios/contrasena PUT Privado/Protegido Cambia la contraseña

Registro de usuario (Sign up)

[POST] https://localhost:8080/api/v1/auth/signup
{
  "nombre": "Fulano De Tal",
  "correo": "[email protected]",
  "contrasena": "admin1234"
}
Output
Registro exitoso

Ingreso de usuario (Login)

[POST] https://localhost:8080/api/v1/auth/login
{
  "correo": "[email protected]",
  "contrasena": "admin1234"
}
Output
{
	"accessToken": {
		"token": "eyJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJGb3JvIEFsdXJhIiwic3ViIjoiYW5hLnNvdXphQHZvbGwubWVkIiwiZXhwIjoxNjg1MjgxMTEwfQ.WEWV8kL0oLQksYyVdkGXU66Wbi5Fu1HQMghGczb7wbsKJicUWW9VJL2oauHhTF3SXPBmpnRIBDqxtEPonPGIkw",
		"fecha_expiracion": "2023-05-28T13:38:30.095+00:00"
	},
	"refreshToken": {
		"token": "4jtstmqo31k4cp0052887h2b8s8b07ai4j4csrso3kgsqlaeg8d1hhhq7sij3a40ocdlf1oo800kquoonh6jlvd2mlmscfpdeiprv6geti9lgt35c6kpmi8u7nqoaqrv",
		"fecha_expiracion": "2023-05-29T13:08:30.092+00:00"
	}
}

Salida de usuario (Log out)

[POST] https://localhost:8080/api/v1/auth/logout
{
  "correo": "[email protected]",
  "contrasena": "admin1234"
}
Output

Respuesta sin cuerpo

Refresh Token

[POST] https://localhost:8080/api/v1/auth/token/refresh
Output
{
	"token": "eyJhbGciOiJIUzUxMiJ9.eyJpc3MiOiJGb3JvIEFsdXJhIiwic3ViIjoiYW5hLnNvdXphQHZvbGwubWVkIiwiZXhwIjoxNjg1MjAwMjQ3fQ.lG8CwawuAAI4xMLDbWSBZfwsdUDnjuXogu--4_cDohLQe4wFUuqnFWK7UNPcWHy9dvZ5kgMSMxtdUw2owW75lg",
	"fecha_expiracion": "2023-05-27T15:10:47.470+00:00"
}

Este es un access token

Cambiar contraseña

[PUT] https://localhost:8080/api/v1/usuarios/contrasena
{
  "contrasenaActual": "admin1234",
  "contrasenaNueva": "user1234",
  "contrasenaConfirmacion": "user1234"
}
Output
Contraseña cambiada con éxito

Asignar rol a usuario

[PUT] https://localhost:8080/api/v1/usuarios/{usuarioId}/roles/{rolId}
Output
> Respuesta sin cuerpo

Entidad Usuario

Atributo Tipo
id long
nombre string
correo string
contrasena string
roles array
publicaciones array
respuestas array

La contraseña está encriptada usando el BCrypt password encoder.

Entidad Refresh Token

Atributo Tipo
id long
token string
fecha_expiracion date
usuario_id long

El refresh token se genera como instancia de SecureRandom (no es un JWT).


Roles

Endpoints para roles


Crear rol

[POST] https://localhost:8080/api/v1/roles
{
  "nombre": "USUARIO"
}
Output

Respuesta sin cuerpo

Entidad Rol

Atributo Tipo
id long
nombre string

Etiquetas

Generalización de Categorías, Subcategorías y Cursos


En el Foro Alura original, las publicaciones están organizadas por categorías, subcategorías y cursos. La información de estas tres entidades es similar y puede modelarse en una única entidad para evitar redundancia en la base de datos.

Por eso, en mi proyecto, tengo una entidad llamada Etiquetas, en lugar de las otras tres: Categorías, Subcategorías y Cursos. Sin embargo, atiendo las peticiones de cada una en un controlador separado.

Entidad Etiqueta

Atributo Tipo
id long
nombre string
nivel enum
etiqueta_padre long
etiquetas_hijas array
publicaciones array

Enum Nivel

Nombre Ordinal
CATEGORIA 0
SUBCATEGORIA 1
CURSO 2

Categorías

Endpoints para Categorías


Endpoint Método Acceso Descripción
/api/v1/categorias/ POST Privado/Admin Crear categoría
/api/v1/categorias/ GET Público Listar Categorías
/api/v1/categorias/{categoriaId} GET Público Obtener categoría por id
/api/v1/categorias/{categoriaId} PUT Privado/Admin Editar categoría
/api/v1/categorias/{categoriaId} DELETE Privado/Admin Eliminar categoría

Crear Categoría

[POST] https://localhost:8080/api/v1/categorias
{
  "nombre": "Programación"
}
Output
{
	"id": 1,
	"nombre": "Programación"
}

Listar Categorías

[GET] https://localhost:8080/api/v1/categorias
Output
{
	"content": [
		{
			"id": 1,
			"nombre": "Programación"
		},
		{
			"id": 2,
			"nombre": "Front End"
		}
	],
	"pageable": {
		"sort": {
			"empty": true,
			"sorted": false,
			"unsorted": true
		},
		"offset": 0,
		"pageNumber": 0,
		"pageSize": 25,
		"unpaged": false,
		"paged": true
	},
	"last": true,
	"totalElements": 2,
	"totalPages": 1,
	"number": 0,
	"size": 25,
	"sort": {
		"empty": true,
		"sorted": false,
		"unsorted": true
	},
	"numberOfElements": 2,
	"first": true,
	"empty": false
}

Obtener Categoría

[GET] https://localhost:8080/api/v1/categorias/{categoriaId}
Output
{
	"id": 1,
	"nombre": "Programación",
	"subcategorias": [
		{
			"id": 2,
			"nombre": "Todas las categorías"
		},
		{
			"id": 3,
			"nombre": "Java"
		},
		{
			"id": 4,
			"nombre": "Lógica de programación"
		}
	]
}

Editar Categoría

[PUT] https://localhost:8080/api/v1/categorias/{categoriaId}
{
  "nombre": "Nueva Categoría"
}
Output
{
	"id": 1,
	"nombre": "Nueva Categoría"
}

Eliminar Categoría

[DELETE] https://localhost:8080/api/v1/categorias/{categoriaId}
{
  "nombre": "Nueva Categoría"
}
Output

Respuesta sin cuerpo


Subcategorías

Endpoints para Subcategorías


Enpoint Método Acceso Descripción
/api/v1/subcategorias/ POST Privado/Admin Crear subcategoría
/api/v1/subcategorias/ GET Público Listar subcategorías
/api/v1/subcategorias/?categoria={categoriaId} GET Público Listar subcategorías por categoria
/api/v1/subcategorias/{categoriaId} GET Público Obtener subcategoría por id
/api/v1/subcategorias/{categoriaId} PUT Privado/Admin Editar subcategoría
/api/v1/subcategorias/{categoriaId} DELETE Privado/Admin Eliminar subcategoría

Crear Subcategoría

[POST] https://localhost:8080/api/v1/subcategorias
{
  "nombre": "Programación",
  "categoria_id": "Java"
}
Output
{
	"id": 5,
	"nombre": "HTML y CSS",
	"categoria": {
		"id": 4,
		"nombre": "Frontend"
	}
}

Listar Subcategorías

[GET] https://localhost:8080/api/v1/subcategorias
Output
	{
	"content": [
		{
			"id": 2,
			"nombre": "Subcategoría 1",
			"categoria": {
				"id": 1,
				"nombre": "Categoría 2"
			}
		}
	],
	"pageable": {
		"sort": {
			"sorted": false,
			"unsorted": true,
			"empty": true
		},
		"pageNumber": 0,
		"pageSize": 25,
		"offset": 0,
		"paged": true,
		"unpaged": false
	},
	"last": true,
	"totalPages": 1,
	"totalElements": 1,
	"sort": {
		"sorted": false,
		"unsorted": true,
		"empty": true
	},
	"numberOfElements": 1,
	"first": true,
	"number": 0,
	"size": 25,
	"empty": false
}

Listar Subcategorías por Categoría

[GET] https://localhost:8080/api/v1/subcategorias?categoria={categoriaId}
Output
{
	"content": [
		{
			"id": 2,
			"nombre": "Java"
		}
	],
	"pageable": {
		"sort": {
			"sorted": false,
			"unsorted": true,
			"empty": true
		},
		"pageNumber": 0,
		"pageSize": 25,
		"offset": 0,
		"paged": true,
		"unpaged": false
	},
	"last": true,
	"totalPages": 1,
	"totalElements": 1,
	"sort": {
		"sorted": false,
		"unsorted": true,
		"empty": true
	},
	"numberOfElements": 1,
	"first": true,
	"number": 0,
	"size": 25,
	"empty": false
}

Obtener Subcategorías por Id

[GET] https://localhost:8080/api/v1/subcategorias/{subcategoriaId}
Output
{
	"id": 2,
	"nombre": "Java",
	"categoria": {
		"id": 1,
		"nombre": "Programación"
	},
	"cursos": [
		{
			"id": 3,
			"nombre": "Lógica de Programación: Primeros Pasos"
		},
		{
			"id": 4,
			"nombre": "Lógica de Programación: Conceptos Primordiales"
		}
	]
}

Editar Subcategoría

[PUT] https://localhost:8080/api/v1/subcategorias/{subcategoriaId}
{
  "nombre": "Subcategoría Modificada",
  "categoria_id": 1
}
Output
{
	"id": 2,
	"nombre": "Nueva Subcategoría 2",
	"categoria": {
		"id": 2,
		"nombre": "Nueva Subcategoría 2"
	}
}

Eliminar Subcategoría

[DELETE] https://localhost:8080/api/v1/subcategorias/{subcategoriaId}
Output

Respuesta sin cuerpo


Cursos

Endpoints para Cursos


Enpoint Método Acceso Descripción
/api/v1/cursos/ POST Privado/Admin Crear curso
/api/v1/cursos/ GET Público Listar cursos
/api/v1/cursos/?categoria={categoriaId} GET Público Listar cursos por categoria
/api/v1/cursos/?subcategoria={subcategoriaId} GET Público Listar cursos por subcategoria
/api/v1/cursos/{categoriaId} GET Público Obtener curso por id
/api/v1/cursos/{categoriaId} PUT Privado/Admin Editar curso
/api/v1/cursos/{categoriaId} DELETE Privado/Admin Eliminar curso

Crear Curso

[POST] https://localhost:8080/api/v1/cursos
{
  "nombre": "Lógica de programación: Primeros pasos",
  "subcategoria_id": 2
}
Output
{
	"id": 3,
	"nombre": "Lógica de programación: Primeros pasos",
	"categoria": {
		"id": 1,
		"nombre": "Programación"
	},
	"subcategoria": {
		"id": 2,
		"nombre": "Lógica de Programación"
	}
}

Listar Cursos

[GET] https://localhost:8080/api/v1/cursos
Output
	{
	"content": [
		{
			"id": 2,
			"nombre": "Subcategoría 1",
			"categoria": {
				"id": 1,
				"nombre": "Categoría 2"
			}
		}
	],
	"pageable": {
		"sort": {
			"sorted": false,
			"unsorted": true,
			"empty": true
		},
		"pageNumber": 0,
		"pageSize": 25,
		"offset": 0,
		"paged": true,
		"unpaged": false
	},
	"last": true,
	"totalPages": 1,
	"totalElements": 1,
	"sort": {
		"sorted": false,
		"unsorted": true,
		"empty": true
	},
	"numberOfElements": 1,
	"first": true,
	"number": 0,
	"size": 25,
	"empty": false
}

Listar Cursos por Categoría

[GET] https://localhost:8080/api/v1/cursos?categoria={categoriaId}
Output
{
	"content": [
		{
			"id": 3,
			"nombre": "Todos los cursos"
		},
		{
			"id": 4,
			"nombre": "C#: conociendo el lenguaje"
		},
		{
			"id": 5,
			"nombre": "C#: introducción a la Orientación de Objetos"
		},
		{
			"id": 6,
			"nombre": "C#: usando herencia e implementando interfaces"
		}
	],
	"pageable": {
		"sort": {
			"empty": true,
			"sorted": false,
			"unsorted": true
		},
		"offset": 0,
		"pageNumber": 0,
		"pageSize": 25,
		"paged": true,
		"unpaged": false
	},
	"last": true,
	"totalElements": 4,
	"totalPages": 1,
	"number": 0,
	"size": 25,
	"sort": {
		"empty": true,
		"sorted": false,
		"unsorted": true
	},
	"first": true,
	"numberOfElements": 1,
	"empty": false
}

Listar Cursos por Subcategoría

[GET] https://localhost:8080/api/v1/cursos?categoria={categoriaId}
Output
{
	"content": [
		{
			"id": 3,
			"nombre": "Todos los cursos"
		},
		{
			"id": 4,
			"nombre": "C#: conociendo el lenguaje"
		},
		{
			"id": 5,
			"nombre": "C#: introducción a la Orientación de Objetos"
		},
		{
			"id": 6,
			"nombre": "C#: usando herencia e implementando interfaces"
		}
	],
	"pageable": {
		"sort": {
			"empty": true,
			"sorted": false,
			"unsorted": true
		},
		"offset": 0,
		"pageNumber": 0,
		"pageSize": 25,
		"paged": true,
		"unpaged": false
	},
	"last": true,
	"totalElements": 4,
	"totalPages": 1,
	"number": 0,
	"size": 25,
	"sort": {
		"empty": true,
		"sorted": false,
		"unsorted": true
	},
	"first": true,
	"numberOfElements": 1,
	"empty": false
}

Obtener Curso por Id

[GET] https://localhost:8080/api/v1/cursos/{cursoId}
Output
{
	"id": 3,
	"nombre": "Curso",
	"categoria": {
		"id": 1,
		"nombre": "Categoría"
	},
	"subcategoria": {
		"id": 2,
		"nombre": "Subcategoría"
	}
}

Editar Curso

[PUT] https://localhost:8080/api/v1/cursos/{cursoId}
{
  "nombre": "Curso Modificado",
  "subcategoria_id": 2
}
Output
{
	"id": 3,
	"nombre": "Curso Modificado",
	"categoria": {
		"id": 1,
		"nombre": "Categoría"
	},
	"subcategoria": {
		"id": 2,
		"nombre": "Subcategoría"
	}
}

Eliminar Curso

[DELETE] https://localhost:8080/api/v1/cursos/{cursoId}
Output

Respuesta sin cuerpo


Publicaciones

Endpoints para Publicaciones


Enpoint Método Acceso Descripción
/api/v1/publicaciones/ POST Privado/Admin Crear publicación
/api/v1/publicaciones/ GET Público Listar publicaciones
/api/v1/publicaciones?curso={cursoId} GET Público Listar publicaciones por curso
/api/v1/publicaciones/{publicacionId} PUT Privado/Admin Editar publicación
/api/v1/publicaciones/{publicacionId} DELETE Privado/Admin Eliminar publicación

Crear Publicacion

[POST] https://localhost:8080/api/v1/publicaciones
{
  "cursoId": 3,
  "titulo": "¿Cómo creo una publicación?",
  "mensaje": "No sé cómo crear publicaciones en el foro"
}
Output
{
	"publicacionId": 1,
	"titulo": "¿Cómo creo una publicación?",
	"mensaje": "No sé cómo crear publicaciones en el foro",
	"fechaCreacion": "2023-05-29T16:39:55.1362709",
	"estado": "NO_RESPONDIDO",
	"totalRespuestas": 0,
	"cursoId": 3,
	"usuario": {
		"usuarioId": 1,
		"nombre": "Ana",
		"correo": "[email protected]"
	}
}

Listar Publicaciones

[GET] https://localhost:8080/api/v1/publicaciones
Output
	{
	"content": [
		{
			"publicacionId": 1,
			"titulo": "¿Cómo creo una publicación?",
			"mensaje": "No sé cómo crear publicaciones en el foro",
			"fechaCreacion": "2023-05-29T16:39:55.136271",
			"estado": "NO_RESPONDIDO",
			"totalRespuestas": 0,
			"cursoId": 3,
			"usuario": {
				"usuarioId": 1,
				"nombre": "Ana",
				"correo": "[email protected]"
			}
		}
	],
	"pageable": {
		"sort": {
			"unsorted": false,
			"empty": false,
			"sorted": true
		},
		"offset": 0,
		"pageNumber": 0,
		"pageSize": 25,
		"paged": true,
		"unpaged": false
	},
	"totalPages": 1,
	"totalElements": 1,
	"last": true,
	"numberOfElements": 1,
	"number": 0,
	"size": 25,
	"sort": {
		"unsorted": false,
		"empty": false,
		"sorted": true
	},
	"first": true,
	"empty": false
}

Listar Publicaciones por Curso

[GET] https://localhost:8080/api/v1/publicaciones?curso={cursoId}
Output
	{
	"content": [
		{
			"publicacionId": 1,
			"titulo": "¿Cómo creo una publicación?",
			"mensaje": "No sé cómo crear publicaciones en el foro",
			"fechaCreacion": "2023-05-29T16:39:55.136271",
			"estado": "NO_RESPONDIDO",
			"respuestas": []
		}
	],
	"pageable": {
		"sort": {
			"unsorted": false,
			"empty": false,
			"sorted": true
		},
		"offset": 0,
		"pageNumber": 0,
		"pageSize": 25,
		"paged": true,
		"unpaged": false
	},
	"totalPages": 1,
	"totalElements": 1,
	"last": true,
	"numberOfElements": 1,
	"number": 0,
	"size": 25,
	"sort": {
		"unsorted": false,
		"empty": false,
		"sorted": true
	},
	"first": true,
	"empty": false
}

Obtener Publicación por Id

[GET] https://localhost:8080/api/v1/publicaciones/{publicacionId}
Output
{
	"id": 3,
	"nombre": "Curso",
	"categoria": {
		"id": 1,
		"nombre": "Categoría"
	},
	"subcategoria": {
		"id": 2,
		"nombre": "Subcategoría"
	}
}

Editar Publicación

[PUT] https://localhost:8080/publicaciones/{publicacionId}
{
  "cursoId": 3,
  "titulo": "Nuevo título publicación",
  "mensaje": "Nuevo mensaje publicación"
}
Output
{
	"publicacionId": 1,
	"titulo": "Nuevo título publicación",
	"mensaje": "Nuevo mensaje publicación",
	"fechaCreacion": "2023-05-21T23:32:34.336265",
	"estado": "NO_RESPONDIDO",
	"totalRespuestas": 0,
	"cursoId": 3,
	"usuario": {
		"usuarioId": 1,
		"nombre": "Ana",
		"correo": "[email protected]"
	}
}

Eliminar Publicación

[DELETE] https://localhost:8080/publicaciones/{publicacionId}
Output

Respuesta sin cuerpo

Entidad Publicación

Atributo Tipo
id long
título string
mensaje int
fecha_creacion date
estado enum
respuestas array
curso long
autor long

Enum EstadoPublicacion

Nombre
NO_RESPONDIDO
RESPONDIDO
SOLUCIONADO
CERRAOD

Respuestas

Endpoints para Respuestas


Enpoint Método Acceso Descripción
/api/v1/publicaciones/{publicacionId}/respuestas POST Privado/Protegido Crear respuesta
/api/v1/publicaciones//{publicacionId}/respuestas GET Público Listar respuestas por publicación
/api/v1/publicaciones/{publicacionId}/respuestas/{respuestaId} PUT Privado/Protegido Editar respuesta
/api/v1/publicaciones/{publicacionId}/respuestas/{respuestaId}/solucion PUT Privado/Protegido Marcar respuesta como solución

Crear Respuesta

[POST] https://localhost:8080/api/v1/publicaciones/{publicacionId}/respuestas
{
  "mensaje": "Mensaje de respuesta"
}
Output
{
	"id": 1,
	"mensaje": "Mensaje de respuesta",
	"fechaCreacion": "2023-05-22T08:07:21.0336658",
	"solucion": false,
	"publicacion_id": 1,
	"autor": {
		"usuarioId": 1,
		"nombre": "Ana",
		"correo": "[email protected]"
	}
}

Listar Respuestas por Publicación

[GET] https://localhost:8080/api/v1/publicaciones/{publicacionId}/respuestas
Output
	{
	"content": [
		{
			"publicacionId": 1,
			"titulo": "¿Cómo creo una publicación?",
			"mensaje": "No sé cómo crear publicaciones en el foro",
			"fechaCreacion": "2023-05-29T16:39:55.136271",
			"estado": "NO_RESPONDIDO",
			"respuestas": []
		}
	],
	"pageable": {
		"sort": {
			"unsorted": false,
			"empty": false,
			"sorted": true
		},
		"offset": 0,
		"pageNumber": 0,
		"pageSize": 25,
		"paged": true,
		"unpaged": false
	},
	"totalPages": 1,
	"totalElements": 1,
	"last": true,
	"numberOfElements": 1,
	"number": 0,
	"size": 25,
	"sort": {
		"unsorted": false,
		"empty": false,
		"sorted": true
	},
	"first": true,
	"empty": false
}

Editar Respuesta

[PUT] https://localhost:8080/api/v1/publicaciones/{publicacionId}/respuestas/{respuestaId}
{
  "mensaje": "Nuevo mensaje respuesta"
}
Output
{
	"id": 1,
	"mensaje": "Nuevo mensaje respuesta",
	"fechaCreacion": "2023-05-22T07:50:44.914698",
	"solucion": false,
	"publicacion_id": 1,
	"autor": {
		"usuarioId": 1,
		"nombre": "Ana",
		"correo": "[email protected]"
	}
}

Escoger Respuesta como Solución

[POST] https://localhost:8080/api/v1/publicaciones/{publicacionId}/respuestas/{respuestaId}/solucion
Output

Respuesta sin cuerpo

Entidad Respuesta

Atributo Tipo
id long
mensaje int
fecha_creacion date
solucion boolean
publicacion long
autor long

challenge-foro-alura's People

Contributors

nicolasbusta avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.